从ViewModel访问附加的属性集合

时间:2019-01-03 12:19:27

标签: c# wpf mvvm binding viewmodel

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

LookupPanelView

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来搜索项目呢?我想避免在可维护性和可测试性方面使用尽可能多的代码隐藏。

3 个答案:

答案 0 :(得分:0)

CollectionViewSourceFilter可以解决问题。

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

答案 1 :(得分:0)

  

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

那么为什么需要视图模型?如果“逻辑应包含在控件中”,则在此实现它。

  

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

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

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

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

答案 2 :(得分:0)

经过一番思考,我为您提供了这种起点。
首先,您需要像下面这样创建控件:

<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开始实施。