【问题标题】:UWP, MVVM and cascade ListViewUWP、MVVM 和级联 ListView
【发布时间】:2017-03-06 10:36:12
【问题描述】:

我对 UWP 和两个 ListView 越来越着迷。场景很简单:我在一个页面中有两个列表视图(listviewlistViewField)。

我有一个像这样的 ViewModel:

public class FormViewModel : BaseViewModel {
    public ObservableCollection<TableInfo> Tables;
}

TableInfo有如下定义:

public class TableInfo {
    /// <summary>
    /// Gets or sets the name.
    /// </summary>
    /// <value>The name.</value>
    public string Name { get; set; }

    /// <summary>
    /// Gets or sets the fields.
    /// </summary>
    /// <value>The fields.</value>
    public List<FieldInfo> Fields { get; set; }
}

最后我想要一些类似的东西:

listview 显示表中的所有 Name(第 1 点)。当用户单击 listview 中的项目时,listviewField 会显示该项目的字段列表(第 2 点)。如果我想更改或添加新字段,用户可以使用表单(第 3 点)。

<ListView x:Name="listView" ItemsSource="{x:Bind vm.Tables, Mode=OneWay}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="mdl:TableInfo">
            <TextBlock Text="{x:Bind Name}" FontSize="25" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
<ListView x:Name="listViewTable" 
          ItemsSource="{Binding ElementName=listView, Path=SelectedItem.Fields}"
          SelectedIndex="{x:Bind vm.SelectedTableInfoIndex, Mode=TwoWay}">
   <ListView.ItemTemplate>
      <DataTemplate x:DataType="mdl:FieldInfo">
          <TextBlock Text="{x:Bind Name}" FontSize="25" />
      </DataTemplate>
   </ListView.ItemTemplate>
</ListView>

如果我在 Tables 开头插入一些项目,我可以在第一个列表视图中看到列表,并且(神奇地)如果我点击一个项目,我可以在第二个列表视图中看到所有字段.但是我尝试从表单中添加一个字段,但没有任何效果。

您对此有什么建议或好的例子吗?提前谢谢你。

更新

在@ibebbs 的帮助下,我更改了listview,您可以在下面找到我的BaseViewModel。我尝试以不同的方式在 viewmodel 中添加一些 TableInfoFieldInfo,但第一个列表视图没有显示任何内容。 如果我在 viewmodel 初始化后立即启动应用程序时添加了一些 TableInfo,则列表视图将按预期工作。

我添加了一个函数来添加一个新的TableInfo

public void AddTableInfo(TableInfo tbl) {
    Tables.Add(tbl);
    RaisePropertyChanged("Tables");
}

第二次更新

正如@MZetko 建议的那样,我在绑定中添加了x:Bind vm.Tables, Mode=OneWay,并且当我添加新项目时列表视图会更新。

最后一个问题是如何将选定的项目与表单连接起来(第 3 点)。

public int SelectedTableInfoIndex
{
    get { return _selectedTableInfoIndex; }
    set {
        _selectedTableInfoIndex = value;
        if (_selectedTableInfoIndex >= 0) {
            _selectedTableInfo = Tables[_selectedTableInfoIndex];
        }
    }
}
private int _selectedTableInfoIndex = -1;

public TableInfo SelectedTableInfo
{
    get {
        return (_selectedTableInfoIndex >= 0) ? Tables[_selectedTableInfoIndex] : null;
    }
    set {
        _selectedTableInfo = value;
        RaisePropertyChanged("SelectedTableInfo");
    }
}
private TableInfo _selectedTableInfo;

那么我的表单数据应该来自我的模型列表视图。

<StackPanel x:Name="stackTable" Orientation="Vertical" Padding="10">
    <TextBlock>Table name</TextBlock>
    <TextBox x:Name="textboxTableName" 
             Text="{Binding ElementName=listView, Path=SelectedItem.Name}" />
    <TextBlock>Project namespace</TextBlock>
    <TextBox x:Name="textboxNameSpace" 
             Text="{Binding ElementName=listView, Path=SelectedItem.ProjectNameSpace}" />
</StackPanel>

这段代码显示了表单中的项目,一个用于表格,另一个用于字段。现在我的问题是:如果我想添加一条新记录或删除一条记录,我该怎么做?

我环顾四周,找到了那些链接

但我不清楚如何在我的情况下使用它。

提前感谢您的帮助!

【问题讨论】:

  • 看起来您已将 ObservableCollection 表定义为公共字段而不是公共属性。绑定和更改通知仅适用于属性,这就是调用此方法时看不到任何更改的原因。
  • 另外,如果某个答案/评论被证明有帮助,您应该将其标记为正确答案和/或投票。

标签: listview mvvm uwp observablecollection


【解决方案1】:

第二个 ListView 绑定到 TableInfo 的 Fields 属性,该属性的类型为 List&lt;FieldInfo&gt;。 List 不支持数据绑定依赖于更新 UI 的更改通知。

List&lt;FieldInfo&gt; 更改为ObservableCollection&lt;FieldInfo&gt;,您应该会看到更好的结果。

顺便说一句,我建议只在第二个 ListView 中绑定 ItemsSource,而不是绑定 DataContext 和 ItemsSource(这需要两次刷新 UI)。应该很简单:

<ListView x:Name="listView" ItemsSource="{x:Bind vm.Tables}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="mdl:TableInfo">
            <TextBlock Text="{x:Bind Name}" FontSize="25" />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
<ListView x:Name="listViewTable" 
          ItemsSource="{Binding ElementName=listView, Path=SelectedItem.Fields}">
   <ListView.ItemTemplate>
      <DataTemplate x:DataType="mdl:FieldInfo">
          <TextBlock Text="{x:Bind Name}" FontSize="25" />
      </DataTemplate>
   </ListView.ItemTemplate>
</ListView>

希望对你有帮助。

【讨论】:

  • 感谢您的回复。如果我在单击某个项目时删除了 DataContext,则第二个列表视图不会显示任何内容。
  • 您是否包含了我对 ItemsSource 绑定字符串所做的更改?
【解决方案2】:

如果我理解正确,问题是添加新字段时第二个列表视图没有更新。

问题在于,字段列表的类型为List&lt;FieldInfo&gt;。 UWP 中的数据绑定基于INotifyPropertyChangedINotifyCollectionChanged 接口。 List 也没有实现,因此无法自动通知用户界面它已更改。为此,您需要将其替换为ObservableCollection&lt;FieldInfo&gt;。当您将项目添加到此集合时,UI 将自动更新为新项目。

不幸的是,这仍然不足以更新字段。 ObservableCollection 类可以观察到类似AddRemove 的项目变化,但不能观察其中单个项目的变化。 为此,您必须确保 FieldInfo 类实现 INotifyPropertyChanged 并且每当您想要观察的属性发生变化时都会调用 PropertyChanged 事件。

接口的最常见实现如下所示:

public event PropertyChangedEventHandler PropertyChanged;

private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
   if (PropertyChanged != null)
   {
       PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
   }
}

现在在你需要调用NotifyPropertyChanged方法的所有属性中:

private string _description = "";

public string Description
{
    get
    {
        return _description;
    }
    set
    {
        _description = value;
        NotifyPropertyChanged();
    }
}

由于[CallerMemberAttribute],您不必指定propertyName 参数,编译器会自动填写。

【讨论】:

  • 感谢您的回复。我已经实现了类似的东西,但它不起作用。例如,如果我尝试从文件中读取一些数据并将每个表 TableInfo 添加到 ObservableCollection&lt;TableInfo&gt;,我在 listview(第一个)上看不到任何内容。
  • 请用更改更新问题,以便我可以看到它:-)
  • x:Bind 语法默认只有一次。您应该将绑定更改为{x:Bind Tables, Mode=OneWay}
  • 你是对的。现在,当我从文件中加载某些内容时工作正常
【解决方案3】:

如果有人需要,我会发布我的BaseViewModel

public class BaseViewModel : INotifyPropertyChanged {
    #region Property changed
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Notifies the property changed.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void NotifyPropertyChanged(string propertyName, Action<bool> message) {
        if (this.PropertyChanged != null) {
            // property changed
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            message?.Invoke(this.IsValid);
        }
    }

    /// <summary>
    /// Raises the property changed.
    /// </summary>
    /// <param name="property">The property.</param>
    protected void RaisePropertyChanged(string property) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }

    // SetField(()=> somewhere.Name = value; somewhere.Name, value) 
    // Advanced case where you rely on another property
    protected bool SetProperty<T>(T currentValue, T newValue, Action DoSet, 
                                  [CallerMemberName] String property = null) {
        if (EqualityComparer<T>.Default.Equals(currentValue, newValue)) return false;
        DoSet.Invoke();
        RaisePropertyChanged(property);
        return true;
    }
    #endregion
}

【讨论】:

    猜你喜欢
    • 2017-10-30
    • 2016-07-13
    • 1970-01-01
    • 2019-04-30
    • 2018-10-10
    • 2021-04-19
    • 1970-01-01
    • 1970-01-01
    • 2017-10-11
    相关资源
    最近更新 更多