简体   繁体   English

WPF:将 ListView 绑定到数据结构

[英]WPF: Bind ListView to a data structure

I would like to bind a data structure like the one below to a ListView or similar component.我想将如下所示的数据结构绑定到 ListView 或类似组件。 Desired result is in the picture below.想要的结果如下图。

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 绑定到一个数据结构

I know it is possible to put the structure content to an array of objects and then bind this array to the ListView, but I would prefer to define the ListView row names in xaml and make direct binding to the underlying structure, for example:我知道可以将结构内容放入对象数组,然后将此数组绑定到 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>

Unfortunately I cannot figure out, how to define "hard-coded" rows in xaml.不幸的是,我无法弄清楚如何在 xaml 中定义“硬编码”行。

Thank you for your ideas.谢谢你的想法。

The way you are adding ListViewItems manually with different bound objects one by one makes it almost impossible to bind to multiple objects.您使用不同的绑定对象手动添加 ListViewItems 的方式几乎不可能绑定到多个对象。 There's 2 ways you can do it though : 1) Make you own ListViewItem (extend and modify it), or 2) In listview declare different properties as columns, eg :有两种方法可以做到:1)让你拥有 ListViewItem(扩展和修改它),或者 2)在 listview 中将不同的属性声明为列,例如:

<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>

And then after you got list of Info object from somewhere just set ListView item source as that list, eg然后从某处获得 Info 对象列表后,只需将 ListView 项目源设置为该列表,例如

LWBoards.ItemsSource = GetListOfBoards();

The simplest solution would be to add a ICollection to your's DataContext .最简单的解决方案是将ICollection添加到您的DataContext Let's say that you're using a MVVM, in which the ListViewBindingExampleViewModel is the DataContext of your view (may it be a Window , a Page or a UserControl ).假设您使用的是 MVVM,其中ListViewBindingExampleViewModel是您视图的DataContext (可能是WindowPageUserControl )。 So, you'd have:所以,你有:

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

    private ListViewBindingExampleViewModel () { }
}

And then, in the xaml:然后,在 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>

It is also worth to mention that the changes of a item of the Infos list would not be propagated to the front-end in this way.还值得一提的是, Infos列表项的更改不会以这种方式传播到前端。

The best way to aproach this problem, in my opinion, would be using Caliburn.Micro , in which the ViewModel has a BindableCollection that would be binded to the ItemsSource of the ListView .在我看来,解决这个问题的最好方法是使用Caliburn.Micro ,其中ViewModel有一个BindableCollection ,它将绑定到ListViewItemsSource For example, in the view model you have this例如,在视图模型中你有这个

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

    private ListViewBindingExampleViewModel () { }
}

And then, you should change your Info class to implement the INotifyPropertyChanged interface, like this:然后,您应该更改Info类以实现INotifyPropertyChanged接口,如下所示:

public struct Info : PropertyChangedBase
{
    private string _port;
    ...

    public string Port { get { return _port; } set { _port= value; NotifyOfPropertyChange(nameof(Port  )); } }
    ...
}

Doing it that way, if you need, the changes in the back-end would be reflected in the front-end.这样做,如果您需要,后端的更改将反映在前端。

Finally it turned out the simplest way is to make a separate view-model class which turns the model data into a collection which can be easily bound to ListView.最后发现最简单的方法是创建一个单独的视图模型类,它将模型数据转换为一个可以轻松绑定到 ListView 的集合。 The solution is quite similar to Rafael Camelo's solution, but it does not fit to the comment.该解决方案与 Rafael Camelo 的解决方案非常相似,但不符合评论。

When using this approach, the field names and descriptions are not written in Xaml, but in the view-model class, but finally this is better solution for my use case.使用这种方法时,字段名称和描述不是用 Xaml 写的,而是写在 view-model 类中,但最后这对我的用例来说是更好的解决方案。 How I finally did it:我最终是如何做到的:

  • Implemented DataRow class implementing INotifyPropertyChanged interface.实现了实现INotifyPropertyChanged接口的DataRow类。 The class provides variable name, value and ListView group.该类提供变量名称、值和 ListView 组。 When the value changes, PropertyChangedEventHandler propagates the change to the user interface.当值更改时, PropertyChangedEventHandler将更改传播到用户界面。
  • Implemented class ListViewModel implementing IReadOnlyList<DataRow>, INotifyCollectionChanged .实现类ListViewModel实现IReadOnlyList<DataRow>, INotifyCollectionChanged This class pulls the required structure from the model and turns it into a collection.此类从模型中提取所需的结构并将其转换为集合。
  • ListViewModel class has internal List<DataRow> containing a fixed list of DataRow objects. ListViewModel类的内部List<DataRow>包含固定的DataRow对象列表。
  • As soon as the structure changes in the model, I simply update all necessary DataRow objects and their changes propagate to UI via PropertyChanged event.一旦模型中的结构发生变化,我只需更新所有必要的DataRow对象,并且它们的更改通过PropertyChanged事件传播到 UI。
  • Implementation of the interface IReadOnlyList<DataRow> is quite simple because I just call internal list's methods.接口IReadOnlyList<DataRow>的实现非常简单,因为我只调用内部列表的方法。
  • INotifyCollectionChanged interface is necessary only if you want to display empty ListView in case there is no structure to be displayed. INotifyCollectionChanged接口仅当您想显示空的 ListView 时才需要,以防没有要显示的结构。 In such cases the internal list remains unchanged, but the IReadOnlyList<DataRow> methods return "empty" data.在这种情况下,内部列表保持不变,但IReadOnlyList<DataRow>方法返回“空”数据。
  • If there would be more different structures, the ListViewModel class could be implemented as a template class and reused many times.如果有更多不同的结构, ListViewModel类可以实现为模板类并多次重用。
  • The ListViewModel class can also perform data conversions more conveniently than Xaml. ListViewModel类还可以比 Xaml 更方便地执行数据转换。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM