【问题标题】:WPF: Bind ListView to a data structureWPF:将 ListView 绑定到数据结构
【发布时间】:2020-02-06 14:27:54
【问题描述】:

我想将如下所示的数据结构绑定到 ListView 或类似组件。想要的结果如下图所示。

public struct Info
{
    public string Port;
    public byte FwVersion;
    public byte ApiVersion;
    public byte BootVersion;
    public char[] BoardId;
    public char[] AfeId;
    public AfeType AfeType;
}

我知道可以将结构内容放入对象数组,然后将此数组绑定到 ListView,但我更愿意在 xaml 中定义 ListView 行名并直接绑定到底层结构,例如:

<ListView>
     <ListView.View>
            <GridView>
                <GridViewColumn Header="Name" Width="120" />
                <GridViewColumn Header="Value" Width="240" />
            </GridView>
     <ListView.View>
     <ListViewItem>
         <ListViewColumn Column="1">Serial port</ListViewColumn>
         <ListViewColumn Column="2">{Binding MyStructure.Port}</ListViewColumn>
     </ListViewItem>
     <ListViewItem>
         <ListViewColumn Column="1">Board ID</ListViewColumn>
         <ListViewColumn Column="2">{Binding MyStructure.BoardId}</ListViewColumn>
     </ListViewItem>
     ...
</ListView>

不幸的是,我不知道如何在 xaml 中定义“硬编码”行。

感谢您的想法。

【问题讨论】:

  • 首先,使用公共属性而不是字段,否则数据绑定将不起作用。还可以考虑使用类而不是结构,以防您希望能够在某些时候编辑项目。
  • @Clemens:你说得对,不幸的是结构是由 API 生成器生成的,因此改变它并不容易。也许使用一些包装器或转换器?
  • 以下是 Microsoft 文档解决此问题的方法:docs.microsoft.com/en-us/archive/msdn-magazine/2008/july/…

标签: c# wpf xaml listview


【解决方案1】:

您手动添加 ListViewItems 与不同的绑定对象的方式几乎不可能绑定到多个对象。不过有两种方法可以做到: 1)让你拥有 ListViewItem (扩展和修改它),或者 2)在列表视图中将不同的属性声明为列,例如:

<ListView x:Name = "LWBoards">
     <ListView.View>
            <GridView>
                <GridViewColumn Header="Serial port" Width="auto" DisplayMemberBinding="{Binding Port}"/>
                <GridViewColumn Header="Board ID" Width="auto" DisplayMemberBinding="{Binding BoardId}"/>
                <...>
                <GridViewColumn Header="AFE ID" Width="auto" DisplayMemberBinding="{Binding AfeId}"/>
                <GridViewColumn Header="AFE Type" Width="auto" DisplayMemberBinding="{Binding AfeType}"/>
            </GridView>
     <ListView.View>
</ListView>

然后在您从某处获得 Info 对象列表后,只需将 ListView 项目源设置为该列表,例如

LWBoards.ItemsSource = GetListOfBoards();

【讨论】:

    【解决方案2】:

    最简单的解决方案是将ICollection 添加到您的DataContext。假设您正在使用 MVVM,其中 ListViewBindingExampleViewModel 是您视图的 DataContext(可能是 WindowPageUserControl)。所以,你会有:

    public class ListViewBindingExampleViewModel
    {
        public List<Info> Infos { get; set; } = new List<Info>();
    
        private ListViewBindingExampleViewModel () { }
    }
    

    然后,在 xaml 中:

    <ListView ItemsSource="{Binding Path=Infos}">
        <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" Width="120" />
                    <GridViewColumn Header="Value" Width="240" />
                </GridView>
         <ListView.View>
         <ListViewItem>
             <ListViewColumn Column="1">Serial port</ListViewColumn>
             <ListViewColumn Column="2">{Binding Path=Port}</ListViewColumn>
         </ListViewItem>
         <ListViewItem>
             <ListViewColumn Column="1">Board ID</ListViewColumn>
             <ListViewColumn Column="2">{Binding Path=BoardId}</ListViewColumn>
         </ListViewItem>
    </ListView>
    

    另外值得一提的是Infos列表中某一项的变化不会以这种方式传播到前端。

    在我看来,解决这个问题的最佳方法是使用Caliburn.Micro,其中ViewModel 有一个BindableCollection,它将绑定到ItemsSourceListView。例如,在视图模型中你有这个

    public class ListViewBindingExampleViewModel
    {
        public BindableCollection<Info> Infos { get; set; } = new BindableCollection<Info>();
    
        private ListViewBindingExampleViewModel () { }
    }
    

    然后,您应该更改您的 Info 类以实现 INotifyPropertyChanged 接口,如下所示:

    public struct Info : PropertyChangedBase
    {
        private string _port;
        ...
    
        public string Port { get { return _port; } set { _port= value; NotifyOfPropertyChange(nameof(Port  )); } }
        ...
    }
    

    这样做,如果你需要,后端的变化会反映在前端。

    【讨论】:

    • 谢谢,但是您的 XAML 代码(可能是从我的伪代码中获取的)不起作用:(
    • 试试 Caliburn.Micro
    【解决方案3】:

    最后发现最简单的方法是创建一个单独的视图模型类,将模型数据转换为一个可以轻松绑定到 ListView 的集合。该解决方案与 Rafael Camelo 的解决方案非常相似,但不适合评论。

    使用这种方法时,字段名称和描述不是用 Xaml 编写的,而是在 view-model 类中,但最后这对于我的用例来说是更好的解决方案。我最终是如何做到的:

    • 实现了DataRow 类实现INotifyPropertyChanged 接口。该类提供变量名称、值和 ListView 组。当值更改时,PropertyChangedEventHandler 会将更改传播到用户界面。
    • 实现类ListViewModel实现IReadOnlyList&lt;DataRow&gt;, INotifyCollectionChanged。此类从模型中提取所需的结构并将其转换为集合。
    • ListViewModel 类具有内部 List&lt;DataRow&gt; 包含 DataRow 对象的固定列表。
    • 一旦模型中的结构发生变化,我只需更新所有必要的 DataRow 对象,它们的更改就会通过 PropertyChanged 事件传播到 UI。
    • 接口IReadOnlyList&lt;DataRow&gt;的实现非常简单,因为我只是调用了内部列表的方法。
    • INotifyCollectionChanged 接口只有在你想显示空的 ListView 时才需要,以防没有要显示的结构。在这种情况下,内部列表保持不变,但 IReadOnlyList&lt;DataRow&gt; 方法返回“空”数据。
    • 如果有更多不同的结构,ListViewModel 类可以实现为模板类并多次重复使用。
    • ListViewModel 类还可以比 Xaml 更方便地执行数据转换。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-01
      • 2011-01-01
      • 2015-04-12
      • 2017-09-29
      • 1970-01-01
      • 1970-01-01
      • 2015-11-03
      • 2011-11-13
      相关资源
      最近更新 更多