【问题标题】:Accessing attached property collection from ViewModel从 ViewModel 访问附加的属性集合
【发布时间】:2019-01-03 12:19:26
【问题描述】:

我有一个自定义控件LookupPanelView,它由一个TextBox 和一个ListBox 组成。它有一个附加属性ItemsSource,ListBox 绑定到该属性,因此可以从控件外部设置绑定的数据。

public partial class LookupPanelView : UserControl
{
    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(LookupPanelView));

    public IEnumerable ItemsSource
    {
        get => (IEnumerable)GetValue(ItemsSourceProperty);
        set => SetValue(ItemsSourceProperty, value);
    }

    public LookupPanelView()
    {
        InitializeComponent();
    }
}

控件的 ItemsSource 绑定到我的主 ViewModel 中的一个属性,该属性决定要显示哪些数据。

public class MainViewModel : ViewModelBase
{
    public ObservableCollection<DomainObject> LookupPanelItems { get; private set; }

    public MainViewModel()
    {
        LookupPanelItems = // Fetch the data to display in the control.
    }
}

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    UseLayoutRounding="True">
<Grid>
    <lookupPanelView:LookupPanelView Grid.Column="0" ItemsSource="{Binding LookupPanelItems}"/>
</Grid>

我想扩展自定义控件,使其具有搜索功能,您可以在其中键入 TextBox 并从 ListBox 中选择匹配项。这个逻辑应该包含在控件中,因为它应该知道如何搜索它自己的项目。我想我需要给控件它自己的 ViewModel 来保存逻辑,但是我如何访问 ViewModel 中的附加属性ItemsSource 来搜索项目?为了可维护性和可测试性,我想尽可能避免使用代码隐藏。

【问题讨论】:

  • 我会为 UserControl 投票反对 ViewModel。此功能是特定于 UserControl 的,不应与 ViewModel 有任何联系,它只应期望数据和过滤器。顺便说一句LookupPanelView 散发着DevExpress 的味道。
  • @XAMlMAX 感谢您的建议。你会在后面的代码中使用老式风格的事件来实现它吗?我不确定你的意思是“DevExpress 的味道”?
  • 用户控件背后的想法是他们应该在不了解视图模型的情况下进行操作。所以你的控件应该过滤它在它的集合中的项目,这是唯一的问题。当我使用 UC 时,这就是我对背后代码所做的事情。只需确保为通用目的实现它,而不依赖于其集合中的特定类型的对象。对于 Dx 没关系 :-)

标签: c# wpf mvvm binding viewmodel


【解决方案1】:

经过一番思考,我想出了这样的起点。
首先你创建你的控件有点像这样:

<UserControl x:Class="SO_App.UC.SearchableListView"
         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:local="clr-namespace:SO_App.UC"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="root"><!-- This allows us to keep the Data Context inheritance -->
    <Grid.Resources>
        <CollectionViewSource Source="{Binding ItemsSource}" x:Key="Items"/> <!-- This is for us to use Filtering and so on -->
    </Grid.Resources>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <TextBox x:Name="txtSearch" Text="{Binding SearchTerm}"/>
    <!-- Placeholder -->
    <TextBlock IsHitTestVisible="False" Text="{Binding SearchTextPlaceHolder,TargetNullValue=Search, FallbackValue=Search}" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0" Foreground="DarkGray">
        <TextBlock.Style>
            <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Visibility" Value="Collapsed"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Text, ElementName=txtSearch}" Value="">
                        <Setter Property="Visibility" Value="Visible"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
    <ListView x:Name="lstItems" Grid.Row="1" ItemsSource="{Binding Source={StaticResource Items}}"/>
</Grid>


根元素保持对用户控件的投标,而我们可以在主窗口中使用来自父元素的正常绑定。 然后在你的 MainWindow.xaml 中你会像这样使用它:

<Window x:Class="SO_App.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="clr-namespace:VM;assembly=VM"
    xmlns:model="clr-namespace:Model;assembly=Model"
    xmlns:converter="clr-namespace:SO_App.Converters"
    xmlns:uc="clr-namespace:SO_App.UC"
    xmlns:local="clr-namespace:SO_App"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <vm:MainViewModel/>
</Window.DataContext>
<Grid>
    <uc:SearchableListView SearchTextPlaceHolder="Search" ItemsSource="{Binding Users}">
        <uc:SearchableListView.Resources>
            <DataTemplate DataType="{x:Type model:User}">
                <Grid>
                    <StackPanel>
                        <TextBlock Text="{Binding ID}"/>
                        <TextBlock Text="{Binding Name}"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
        </uc:SearchableListView.Resources>
    </uc:SearchableListView>
</Grid>


为了这篇文章,这里是ViewModel

public class MainViewModel : BaseViewModel
{
    public MainViewModel()
    {
        Users = new List<User>();
        for (int i = 0; i < 6; i++)
        {
            Users.Add(new User
            {
                ID = i,
                Name = $"John the {i + 1}",
                State = i % 2 == 0 ? "CA" : "IL",
                Cases = new List<Case>() { new Case { CaseID = (i + 1) * 10, Vendor = ((i + 1) * 10) - 2 }, new Case { CaseID = (i + 1) * 10, Vendor = ((i + 1) * 10) - 2 } }
            });
        }
    }
}  

这里是用户对象:

namespace Model
{
    public class User//Ideally you would have INPC implemented here
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string State { get; set; }
        public List<Case> Cases { get; set; }
    }
}  

希望这为您提供了足够的信息,以便您朝着正确的方向开始实施,并尽可能多地使用 MvvM。

【讨论】:

    【解决方案2】:

    CollectionViewSourceFilter 可以解决问题。

    Here 是使用 CollectionViewSource 进行搜索时使用过滤器的基本示例

    【讨论】:

    • 是否可以使用这种技术突出匹配项而不影响列表的其余部分? IE。不过滤列表,只选择匹配项
    • 视图不返回被过滤的条目。您不妨注入一个谓词来决定要突出显示哪些。
    【解决方案3】:

    这个逻辑应该包含在控件中,因为它应该知道如何搜索它自己的项目。

    那你为什么需要一个视图模型呢?如果“逻辑应该包含在控件中”,那么在那里实现它。

    我想我需要给控件它自己的 ViewModel 来保存逻辑,但是我如何访问 ViewModel 中的附加属性 ItemsSource 来搜索项目?

    这与您的第一句话相矛盾,但如果控件出于某种原因确实需要自己的视图模型并且视图模型需要访问控件,您可以在创建视图时简单地注入对控件的引用型号,例如:

    public LookupPanelView()
    {
        InitializeComponent();
        this.DataContext = new ViewModel(this);
    }
    

    但您可能想要的是使用默认模板创建自定义控件。这只是一个继承自 Control 的类,没有代码隐藏或 XAML 文件。示例请参考this 教程。 UserControl 更像是一个复合视图,而不是具有自己自定义逻辑的自定义控件。

    【讨论】:

    • 请注意,如果您像这样设置 UC 的 DataContext,那么 ItemsSource 绑定将在 UC 中查找该属性,而不是正在使用它的 DataContext。您需要保留 DataContext 继承,以便正确解析绑定。
    猜你喜欢
    • 1970-01-01
    • 2016-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-25
    • 2018-05-08
    • 1970-01-01
    • 2018-09-03
    相关资源
    最近更新 更多