是否有一个 XAML 等同于 nameof?
Is there a XAML equivalent to nameof?
我正在使用 DevExpress 的 WPF 树列表视图,我遇到了一个我认为更普遍的问题,该问题与重命名用作项目源的对象的属性有关。在树列表视图中,需要指定 ParentFieldName 和 KeyFieldName(用于确定树的结构)。这些字段是字符串。
这导致了重构代码的问题。例如,将我用作 ItemSource 的对象的 属性 重命名将破坏树视图,因为 ParentFieldName 和 KeyFieldName 不再与 属性 名称同步。我通过在我的视图模型 "ParentFieldName" 和 "KeyFieldName" 中创建属性来解决这个问题,它们使用 nameof 将 属性 名称呈现给视图。
这是该控件的简化版本:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<dxg:TreeListControl AutoGenerateColumns="AddNew"
EnableSmartColumnsGeneration="True" ItemsSource="{Binding Results}"
SelectionMode="Row">
<dxg:TreeListControl.View>
<dxg:TreeListView
ParentFieldName="{Binding ParentIdFieldName}" KeyFieldName="{Binding NodeIdFieldName}"
ShowHorizontalLines="False" ShowVerticalLines="False"
ShowNodeImages="True"/>
</dxg:TreeListControl.View>
</dxg:TreeListControl>
</UserControl>
和视图模型:
using DevExpress.Mvvm;
public sealed class ViewModel : ViewModelBase
{
public string ParentIdFieldName => nameof(TreeNode.ParentId);
public string NodeIdFieldName => nameof(TreeNode.NodeId);
public ObservableCollection<TreeNode> Results
{
get => GetProperty(() => Results);
set => SetProperty(() => Results, value);
}
}
和树节点:
public sealed class TreeNode
{
public int ParentId {get; set;}
public int NodeId {get; set;}
}
我的解决方案运行良好,但我想知道是否有更好的方法。例如,我可以在 XAML 中做一些相当于 nameof 调用的事情,而不是绑定到视图模型中的这个 ParentIdFieldName 和 NodeIdFieldName 吗?
我意识到这可以描述为 DevExpress 控件的问题。但是,我感兴趣的是我用来解决这个问题的方法是否可以改进。有没有一种方法可以直接在 XAML 中以更简单的方式执行此操作?
如果我提供的代码无法编译,我提前道歉。我已经大大减少了我正在使用的内容以提供一个示例。
您可以创建自定义标记扩展。
例如:
[ContentProperty(nameof(Member))]
public class NameOfExtension : MarkupExtension
{
public Type Type { get; set; }
public string Member { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
throw new ArgumentNullException(nameof(serviceProvider));
if (Type == null || string.IsNullOrEmpty(Member) || Member.Contains("."))
throw new ArgumentException("Syntax for x:NameOf is Type={x:Type [className]} Member=[propertyName]");
var pinfo = Type.GetRuntimeProperties().FirstOrDefault(pi => pi.Name == Member);
var finfo = Type.GetRuntimeFields().FirstOrDefault(fi => fi.Name == Member);
if (pinfo == null && finfo == null)
throw new ArgumentException($"No property or field found for {Member} in {Type}");
return Member;
}
}
示例用法:
出于某种原因,我的设计人员无法很好地使用 G.Sharada 的解决方案,尽管它在运行时运行良好。
我走的路线略有不同:
public class NameOfExtension : MarkupExtension
{
private readonly PropertyPath _propertyPath;
public NameOfExtension(Binding binding)
{
_propertyPath = binding.Path;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var indexOfLastVariableName = _propertyPath.Path.LastIndexOf('.');
return _propertyPath.Path.Substring(indexOfLastVariableName + 1);
}
}
这样我可以做到:
<TextBlock Text="{local:NameOf {Binding Property1.Property2}}" />
这显然不是替代品,因为我们可能没有要绑定的实例化对象。我想我可以有 2 个构造函数,一个接受绑定,另一个接受成员字符串。
我正在使用 DevExpress 的 WPF 树列表视图,我遇到了一个我认为更普遍的问题,该问题与重命名用作项目源的对象的属性有关。在树列表视图中,需要指定 ParentFieldName 和 KeyFieldName(用于确定树的结构)。这些字段是字符串。
这导致了重构代码的问题。例如,将我用作 ItemSource 的对象的 属性 重命名将破坏树视图,因为 ParentFieldName 和 KeyFieldName 不再与 属性 名称同步。我通过在我的视图模型 "ParentFieldName" 和 "KeyFieldName" 中创建属性来解决这个问题,它们使用 nameof 将 属性 名称呈现给视图。
这是该控件的简化版本:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<dxg:TreeListControl AutoGenerateColumns="AddNew"
EnableSmartColumnsGeneration="True" ItemsSource="{Binding Results}"
SelectionMode="Row">
<dxg:TreeListControl.View>
<dxg:TreeListView
ParentFieldName="{Binding ParentIdFieldName}" KeyFieldName="{Binding NodeIdFieldName}"
ShowHorizontalLines="False" ShowVerticalLines="False"
ShowNodeImages="True"/>
</dxg:TreeListControl.View>
</dxg:TreeListControl>
</UserControl>
和视图模型:
using DevExpress.Mvvm;
public sealed class ViewModel : ViewModelBase
{
public string ParentIdFieldName => nameof(TreeNode.ParentId);
public string NodeIdFieldName => nameof(TreeNode.NodeId);
public ObservableCollection<TreeNode> Results
{
get => GetProperty(() => Results);
set => SetProperty(() => Results, value);
}
}
和树节点:
public sealed class TreeNode
{
public int ParentId {get; set;}
public int NodeId {get; set;}
}
我的解决方案运行良好,但我想知道是否有更好的方法。例如,我可以在 XAML 中做一些相当于 nameof 调用的事情,而不是绑定到视图模型中的这个 ParentIdFieldName 和 NodeIdFieldName 吗?
我意识到这可以描述为 DevExpress 控件的问题。但是,我感兴趣的是我用来解决这个问题的方法是否可以改进。有没有一种方法可以直接在 XAML 中以更简单的方式执行此操作?
如果我提供的代码无法编译,我提前道歉。我已经大大减少了我正在使用的内容以提供一个示例。
您可以创建自定义标记扩展。
例如:
[ContentProperty(nameof(Member))]
public class NameOfExtension : MarkupExtension
{
public Type Type { get; set; }
public string Member { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
throw new ArgumentNullException(nameof(serviceProvider));
if (Type == null || string.IsNullOrEmpty(Member) || Member.Contains("."))
throw new ArgumentException("Syntax for x:NameOf is Type={x:Type [className]} Member=[propertyName]");
var pinfo = Type.GetRuntimeProperties().FirstOrDefault(pi => pi.Name == Member);
var finfo = Type.GetRuntimeFields().FirstOrDefault(fi => fi.Name == Member);
if (pinfo == null && finfo == null)
throw new ArgumentException($"No property or field found for {Member} in {Type}");
return Member;
}
}
示例用法:
出于某种原因,我的设计人员无法很好地使用 G.Sharada 的解决方案,尽管它在运行时运行良好。
我走的路线略有不同:
public class NameOfExtension : MarkupExtension
{
private readonly PropertyPath _propertyPath;
public NameOfExtension(Binding binding)
{
_propertyPath = binding.Path;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var indexOfLastVariableName = _propertyPath.Path.LastIndexOf('.');
return _propertyPath.Path.Substring(indexOfLastVariableName + 1);
}
}
这样我可以做到:
<TextBlock Text="{local:NameOf {Binding Property1.Property2}}" />
这显然不是替代品,因为我们可能没有要绑定的实例化对象。我想我可以有 2 个构造函数,一个接受绑定,另一个接受成员字符串。