我有一个自定义控件LookupPanelView
,它由一个TextBox
和一个ListBox
组成。它具有ListBox绑定到的附加属性ItemsSource
,因此可以从控件外部设置绑定数据。
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
来搜索项目呢?我想避免在可维护性和可测试性方面使用尽可能多的代码隐藏。
答案 0 :(得分:0)
CollectionViewSource和Filter可以解决问题。
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开始实施。