将命名元组绑定到XAML DataGrid

huangapple go评论61阅读模式
英文:

Binding named tuple to XAML DataGrid

问题

在我的WPF应用程序中,我有许多视图模型,现在需要一个像"属性-值"一样的网格。

我尝试构建一个通用的视图模型,可以“注入”到现有的视图模型中,以便我可以在新窗口中使用它们,几乎不需要任何努力。为此,我编写了一个带有可观察集合的简单接口:

public interface IPropertyGridVM
{
    ObservableCollection<(string Prop, object Val)> PropValueList { get; set; }
}

在需要新属性值网格的一个视图模型中:

public class ExistingVM : ViewModelBase<Model>, IPropertyGridVM
{
    public ObservableCollection<(string Prop, object Val)> PropValueList { get; set; }

    ExistingVM() : base(new Model())
    {
        // "旧"视图模型初始化
        initPropValueList();
    }

    ExistingVM(Model model) : base(model)
    {
        // "旧"视图模型初始化
        initPropValueList();
    }

    private void initPropValueList()
    {
        PropValueList = new ObservableCollection<(string Prop, object Val)>()
        {
            (nameof(Prop1), Prop1),
            // ...
        };
    }
}

遵循应用程序的现有约定:

// 这段代码位于对话框管理器内
public void ShowPropertiesDialog<T>(T propValueLikeVm)
{
    if (propValueLikeVm is IPropertyGridVM)
    {
        // 创建对话框
        PropertyGridDialog dialog = new PropertyGridDialog();
        // 分配数据上下文
        dialog.DataContext = propValueLikeVm;
        // 到这里都没问题,视图模型已正确初始化并包含我预期的内容
        dialog.ShowDialog();
    }
}

现在,在我的通用XAML对话框中出现问题:

<!-- Intellisense提醒我在绑定PropValueList时存在一些问题:“未找到PropValueList的DataContext值”-->
<DataGrid ItemsSource="{Binding Path=PropValueList}">
    <DataGrid.Columns>
        <DataGridTextColumn Width="2*" Binding="{Binding Path=Prop}">
            <!-- 文本列样式 -->
        </DataGridTextColumn>
        <DataGridTextColumn Width="2*" Binding="{Binding Path=Val}">
            <!-- 文本列样式 -->
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

这是正确的:在运行时,我有绑定错误,告诉我“在ValueTuple'2类型的对象中未找到Prop属性”和“在ValueTuple'2类型的对象中未找到Val属性”,但我无法弄清楚原因。

有什么提示吗?

英文:

In my WPF application, I have a lot of view models that, now, require a "property-value" like grid.

I'm trying to build a generic purpose view model that can be "injected" in existing ones so that I can use them with the new window with little no effort. To do this, I've written a simple interface with an observable collection:

public interface IPropertyGridVM
{
    ObservableCollection&lt;(string Prop, object Val)&gt; PropValueList
    {
        get;
        set;
    }
}

And, in one of the View Models that needs the new property-value grid:

public class ExistingVM : ViewModelBase&lt;Model&gt;, IPropertyGridVM
{
    public ObservableCollection&lt;(string Prop, object Val)&gt; PropValueList
    {
        get;
        set;
    }

    ExistingVM()
         : base(new Model())
    {
        // &quot;old&quot; vm initialization
        initPropValueList();
    }

    ExistingVM(Model model)
         : base(model)
    {
        // &quot;old&quot; vm initialization
        initPropValueList();
    }

    private void initPropValueList()
    {
         PropValueList = new ObservableCollection&lt;(string Prop, object Val)&gt;()
         {
             (nameof(Prop1), Prop1),
             // ...
         }
    }
}

Following the existing convention of the application:

// This piece of code is inside a Dialog Manager    
public void ShowPropertiesDialog&lt;T&gt;(T propValueLikeVm)
{
    if (propValueLikeVm is IPropertyGridVM)
    {
        // create the dialog
        PropertyGridDialog dialog = new PropertyGridDialog();
        // assign the datacontext
        dialog.DataContext = propValueLikeVm;
        // till here is all ok, VM is correctly initialized and contains what I expected
        dialog.ShowDialog();
    }
}

Now, in my general XAML dialog come the troubles:

&lt;...
     xmlns:vm=&quot;clr-namespace:ViewModels;assembly=ViewModels&quot;
     d:DataContext=&quot;{d:DesignData Type={x:Type vm:IPropertyGridVM},
                             IsDesignTimeCreatable=True}&quot;
     mc:Ignorable=&quot;d&quot;&gt;
&lt;!-- dialog styling and definition --&gt;
&lt;!-- Intellisense warns me of some problems in binding the PropValueList: &quot;No DataContext value found for PropValueList&quot;--&gt;
&lt;DataGrid ItemsSource=&quot;{Binding Path=PropValueList}&quot;&gt;
     &lt;DataGrid.Columns&gt;
			&lt;DataGridTextColumn Width=&quot;2*&quot; Binding=&quot;{Binding Path=Prop}&quot;&gt;
                   &lt;!-- Text column styling --&gt;
            &lt;/DataGridTextColumn&gt;
            &lt;DataGridTextColumn Width=&quot;2*&quot; Binding=&quot;{Binding Path=Val}&quot;&gt;
                   &lt;!-- Text column styling --&gt;
            &lt;/DataGridTextColumn&gt;
     &lt;/DataGrid.Columns&gt;
&lt;/DataGrid&gt;

And it's right: at run-time I have binding errors that tell me that "The property Prop has not been found in the object of type ValueTuple'2" and "The property Val has not been found in the object of type ValueTuple'2", but I can't figure why.

Any hints?

答案1

得分: 1

以下是翻译好的内容:

"Prop" 和 "Val" 这两个名称只在编译之前真正存在。它们实际上被命名为 "Item1" 和 "Item2"。编译器进行了一些魔法操作,以便您在源代码中使用更好的名称。但是,这些是字段而不是属性,您可能需要属性来在 WPF 中进行绑定。我建议您添加自己的类:

public class PropVal : INotifyPropertyChanged
{
    public string Prop { get; }
    public object Val { get; }

    public PropVal(string prop, object val) => (Prop, Val) = (prop, val);

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

我期望这将更加可靠。如果您愿意,可以从相应的 ValueTuple 中添加隐式或显式转换。

英文:

The names Prop and Val are only really present before compilation. They are really named Item1 and Item2. The compiler does some magic to let you use better names in the source. However, these are fields and not properties, and you might need properties to bind to in WPF. I would recommend just adding your own class:

public class PropVal : INotifyPropertyChanged{
    public string Prop {get;}
    public object Val {get;}
    public PropVal(string prop, object val) =&gt; (Prop, Val) = (prop, val);
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

I would expect that to be more reliable. Add a implicit or explicit conversion from the corresponding valuetuple if you wish.

huangapple
  • 本文由 发表于 2023年4月11日 16:01:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/75983658.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定