#c# #wpf #data-binding #wpf-controls #dependency-properties
Вопрос:
Я создал пользовательский элемент управления, который, по сути, содержит набор комбинаций, называемых SearchParamsControl. Элемент управления SearchParamsControl содержит все элементы пользовательского интерфейса, необходимые для настройки всего в классе SearchParams:
Мой поискпарамконтроль:
/// lt;summarygt; /// Interaction logic for SearchParamsControl.xaml /// lt;/summarygt; public partial class SearchParamsControl : INotifyPropertyChanged { public SearchParamsControl() { InitializeComponent(); } /// lt;summarygt; /// Radius entries bound to combobox /// lt;/summarygt; public Dictionarylt;double, stringgt; RadiusEntries { get; set; } = new Dictionarylt;double, stringgt;() { {0, "This area only" }, { 0.25, "Within 1/4 mile" }, { 0.5, "Within 1/2 mile" }, { 1, "Within 1 mile" }, { 3, "Within 3 miles" }, { 5, "Within 5 miles" }, { 10, "Within 10 miles" }, { 15, "Within 15 miles" }, { 20, "Within 20 miles" }, { 30, "Within 30 miles" }, { 40, "Within 40 miles" } }; public Dictionarylt;PropertyTypeEnum, stringgt; PropertyTypes =gt; PropertyTypeDictionary; /// lt;summarygt; /// Prices bound to combo box /// lt;/summarygt; public Listlt;intgt; Prices { get; set; } = new Listlt;intgt;() { 0, 50000, 60000, 70000, 80000, 90000, 100000, 110000, 120000, 125000, 130000, 150000, 200000, 250000, 300000, 325000, 375000, 400000, 425000, 450000, 475000, 500000, 550000, 600000, 650000, 700000, 800000, 900000, 1000000, 1250000, 1500000, 1750000, 2000000, 2500000, 3000000, 4000000, 5000000, 7500000, 10000000, 15000000, 20000000 }; /// lt;summarygt; /// Bedrooms bound to combobox /// lt;/summarygt; public Listlt;intgt; Bedrooms { get; set; } = new Listlt;intgt;() { 0, 1, 2, 3, 4, 5 }; public StringTrieSet SearchString { get { return RightMoveCodes.RegionTree; } } public double Radius { get =gt; SearchParams.Radius; set { if (SearchParams.Radius != value) { SearchParams.Radius = value; OnSearchParamsChanged(); } } } public int MinBedrooms { get { return SearchParams.MinBedrooms; } set { if (SearchParams.MinBedrooms != value) { SearchParams.MinBedrooms = value; OnSearchParamsChanged(); } } } public int MaxBedrooms { get { return SearchParams.MaxBedrooms; } set { if (SearchParams.MaxBedrooms != value) { SearchParams.MaxBedrooms = value; OnSearchParamsChanged(); } } } public int MinPrice { get { return SearchParams.MinPrice; } set { if (SearchParams.MinPrice != value) { SearchParams.MinPrice = value; OnSearchParamsChanged(); } } } public int MaxPrice { get { return SearchParams.MaxPrice; } set { if (SearchParams.MaxPrice != value) { SearchParams.MaxPrice = value; OnSearchParamsChanged(); } } } public SortType SortType { get { return SearchParams.Sort; } set { if (SearchParams.Sort != value) { SearchParams.Sort = value; OnSearchParamsChanged(); } } } public SearchParams SearchParams { get { SearchParams searchParams = (SearchParams)GetValue(SearchParamsProperty); return searchParams; } set { SetValue(SearchParamsProperty, value); } } // Using a DependencyProperty as the backing store for MySelectedItem. This enables animation, styling, binding, etc... public static readonly DependencyProperty SearchParamsProperty = DependencyProperty.Register("SearchParams", typeof(SearchParams), typeof(SearchParamsControl), new PropertyMetadata(new SearchParams(), OnSearchParamsPropertyChanged)); public event PropertyChangedEventHandler PropertyChanged; private static void OnSearchParamsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { SearchParamsControl c = d as SearchParamsControl; if (c != null) { c.OnSearchParamsChanged(); } } private void OnSearchParamsChanged() { OnPropertyChanged(nameof(SearchParams)); } private void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } }
И xml:
lt;UserControl x:Class="RightMoveApp.UserControls.SearchParamsControl" 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:RightMoveApp.UserControls" xmlns:System="clr-namespace:System;assembly=System.Runtime" xmlns:StyleAlias="clr-namespace:RightMove;assembly=RightMove" xmlns:viewModel="clr-namespace:RightMoveApp.ViewModel" xmlns:dataTypes="clr-namespace:RightMove.DataTypes;assembly=RightMove" xmlns:valueconverters="clr-namespace:RightMoveApp.UserControls.ValueConverters" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" x:Name="uc"gt; lt;UserControl.DataContextgt; lt;viewModel:SearchParamsControlViewModel/gt; lt;/UserControl.DataContextgt; lt;UserControl.Resourcesgt; lt;ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}"gt; lt;ObjectDataProvider.MethodParametersgt; lt;x:Type TypeName="dataTypes:SortType"/gt; lt;/ObjectDataProvider.MethodParametersgt; lt;/ObjectDataProvidergt; lt;valueconverters:PropertyTypeConverter x:Key="PropertyTypeConverter" x:Shared="False"/gt; lt;Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ComboStyle}"gt; lt;Setter Property="Margin" Value="0,0,0,1"/gt; lt;/Stylegt; lt;/UserControl.Resourcesgt; lt;Grid x:Name="LayoutRoot"gt; lt;Grid.ColumnDefinitionsgt; lt;ColumnDefinition Width="Auto"/gt; lt;ColumnDefinition Width="1*"/gt; lt;/Grid.ColumnDefinitionsgt; lt;Grid.RowDefinitionsgt; lt;RowDefinition Height="Auto"/gt; lt;RowDefinition Height="Auto"/gt; lt;RowDefinition Height="Auto"/gt; lt;RowDefinition Height="Auto"/gt; lt;RowDefinition Height="Auto"/gt; lt;RowDefinition Height="Auto"/gt; lt;/Grid.RowDefinitionsgt; lt;Label Grid.Column="0" Grid.Row="0" Content="Search area"/gt; lt;local:AutoCompleteComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding ElementName=uc, Path=SearchString}" SelectedValue="{Binding Path=RegionLocation, Mode=TwoWay}"/gt; lt;Label Grid.Column="0" Grid.Row="1" Content="Search radius"/gt; lt;ComboBox Template="{DynamicResource ComboBoxTemplate1}" Grid.Column="1" Grid.Row="1" Name="comboSearchRadius" ItemsSource="{Binding ElementName=uc, Path=RadiusEntries}" SelectedValuePath="Key" SelectedValue="{Binding ElementName=uc, Path=Radius, Mode=TwoWay}"gt; lt;ComboBox.ItemTemplategt; lt;DataTemplategt; lt;StackPanel Orientation="Horizontal"gt; lt;TextBlock Text="{Binding Value}"/gt; lt;/StackPanelgt; lt;/DataTemplategt; lt;/ComboBox.ItemTemplategt; lt;/ComboBoxgt; lt;Label Grid.Column="0" Grid.Row="2" Content="Price range (£)"/gt; lt;Grid Grid.Column="1" Grid.Row="2"gt; lt;Grid.ColumnDefinitionsgt; lt;ColumnDefinition Width="1*"/gt; lt;ColumnDefinition Width="Auto"/gt; lt;ColumnDefinition Width="1*"/gt; lt;/Grid.ColumnDefinitionsgt; lt;Grid.RowDefinitionsgt; lt;RowDefinition Height="Auto"/gt; lt;/Grid.RowDefinitionsgt; lt;ComboBox Grid.Column="0" Grid.Row="0" Name="comboMinPrice" ItemsSource="{Binding ElementName=uc, Path=Prices}" SelectedItem="{Binding ElementName=uc, Path=MinPrice, Mode=TwoWay}"gt; lt;ComboBox.ItemTemplategt; lt;DataTemplategt; lt;StackPanel Orientation="Horizontal"gt; lt;TextBlock Text="{Binding}"/gt; lt;/StackPanelgt; lt;/DataTemplategt; lt;/ComboBox.ItemTemplategt; lt;/ComboBoxgt; lt;Label Grid.Column="1" Grid.Row="0" Content="to"/gt; lt;ComboBox Grid.Column="2" Grid.Row="0" Name="comboMaxPrice" ItemsSource="{Binding ElementName=uc, Path=Prices}" SelectedItem="{Binding ElementName=uc, Path=MaxPrice}"gt; lt;ComboBox.ItemTemplategt; lt;DataTemplategt; lt;StackPanel Orientation="Horizontal"gt; lt;TextBlock Text="{Binding}"/gt; lt;/StackPanelgt; lt;/DataTemplategt; lt;/ComboBox.ItemTemplategt; lt;/ComboBoxgt; lt;/Gridgt; lt;Label Grid.Column="0" Grid.Row="3" Content="No. of bedrooms"/gt; lt;Grid Grid.Column="1" Grid.Row="3"gt; lt;Grid.ColumnDefinitionsgt; lt;ColumnDefinition Width="1*"/gt; lt;ColumnDefinition Width="Auto"/gt; lt;ColumnDefinition Width="1*"/gt; lt;/Grid.ColumnDefinitionsgt; lt;Grid.RowDefinitionsgt; lt;RowDefinition Height="Auto"/gt; lt;/Grid.RowDefinitionsgt; lt;ComboBox Grid.Column="0" Grid.Row="0" Name="comboMinBedrooms" ItemsSource="{Binding ElementName=uc, Path=Bedrooms}" SelectedItem="{Binding ElementName=uc, Path=MinBedrooms, Mode=TwoWay}"gt; lt;ComboBox.ItemTemplategt; lt;DataTemplategt; lt;StackPanel Orientation="Horizontal"gt; lt;TextBlock Text="{Binding}"/gt; lt;/StackPanelgt; lt;/DataTemplategt; lt;/ComboBox.ItemTemplategt; lt;/ComboBoxgt; lt;ComboBox Grid.Column="3" Grid.Row="0" Name="comboMaxBedrooms" ItemsSource="{Binding ElementName=uc, Path=Bedrooms}" SelectedItem="{Binding ElementName=uc, Path=MaxBedrooms, Mode=TwoWay}"gt; lt;ComboBox.ItemTemplategt; lt;DataTemplategt; lt;StackPanel Orientation="Horizontal"gt; lt;TextBlock Text="{Binding}"/gt; lt;/StackPanelgt; lt;/DataTemplategt; lt;/ComboBox.ItemTemplategt; lt;/ComboBoxgt; lt;Label Grid.Column="1" Grid.Row="0" Content="to"/gt; lt;/Gridgt; lt;Label Grid.Column="0" Grid.Row="4" Content="Sort Type"/gt; lt;ComboBox Grid.Column="1" Grid.Row="4" Name="comboSort" ItemsSource="{Binding Source={StaticResource dataFromEnum}}" SelectedItem="{Binding ElementName=uc, Path=SortType, Mode=TwoWay}"gt; lt;/ComboBoxgt; lt;/Gridgt;
I want to bind to the SearchParams dependency property in my MainWindow, which contains the SearchParamsControl:
lt;GroupBox Grid.Column="0" Grid.Row="0" Header="Search Params" Panel.ZIndex="10"gt; lt;controls:SearchParamsControl x:Name="searchControl" SearchParams="{Binding Path=SearchParams, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding Path=IsSearching, Converter={StaticResource BooleanToReverseConverter}}"gt; lt;/controls:SearchParamsControlgt; lt;/GroupBoxgt; lt;Button x:Name="btnSearch" Grid.Column="0" Grid.Row="1" Content="Search" IsDefault="True" Command="{Binding SearchAsyncCommand}"/gt;
Эта привязка работает нормально. Но, как вы можете видеть, у меня в главном окне есть кнопка, которая подключена к команде. Я хочу знать, когда изменилось свойство в SearchParams, чтобы я мог вызвать что-то вроде SearchCommandAsync.RaiseCanExecuteChanged (), чтобы я мог обновить состояние кнопки поиска. Как я могу с этим справиться?
Обратите внимание, что я не хочу использовать следующий закомментированный код (из моего командного класса), который, как мне кажется, работает, но я скорее хочу иметь возможность уведомлять, что у SearchParams изменено свойство, и поэтому нам нужно снова вызвать CanExecute:
//public event EventHandler CanExecuteChanged //{ // add // { // CommandManager.RequerySuggested = value; // } // remove // { // CommandManager.RequerySuggested -= value; // } //} public event EventHandler CanExecuteChanged; public void RaiseCanExecuteChanged() { //CommandManager.InvalidateRequerySuggested(); if (CanExecuteChanged != null) { CanExecuteChanged(this, new EventArgs()); } }
Кроме того, я не хочу изменять параметры поиска с помощью обработчика событий PropertyChanged (представьте, что это закрытая библиотека классов, и я не могу ее изменить).
Ответ №1:
Если я правильно понимаю вашу настройку, SearchParams
свойство SearchParamsControl
в представлении должно устанавливать свойство SearchParams
источника с привязкой к данным модели представления при каждом изменении свойства элемента управления.
Затем вы можете вызвать CanExecuteChanged
метод команды в установщике SearchParams
исходного свойства.
Комментарии:
1. В том-то и дело, что свойство источника SearchParams (если вы имеете в виду параметры поиска в модели mainwindowviewмодели моего главного окна) никогда не выполняется, скорее всего, потому, что это параметры поиска. Радиус, скажем, который изменен. Однако, если я принудительно установлю значение CanExecute в значение true, я могу проверить параметры поиска в модели mainviewмодели в отладчике и подтвердить, что свойство Radius SearchParams правильно обновлено, но точка останова установщика параметров поиска никогда не будет достигнута. Я даже попытался ввести параметры поиска=параметры поиска в настройках Radius в пользовательском контроле, чтобы попытаться принудительно вызвать сеттер в MainViewModel
2. Значит, привязка не работает? Кстати, вам следует удалить
lt;UserControl.DataContextgt;
элемент из элемента управления. Элемент управления должен наследовать егоDataContext
от родительского элемента.3. Мои извинения, я удалил это (я полностью удалил SearchControlViewModel). Привязка работает, измененные свойства параметров поиска верны в модели MainWindowViewModel. Когда я устанавливаю точку останова в своем методе ExecuteSearch, я отлаживаю параметры поиска, и мои изменения отображаются правильно. Но сам SearchParams не изменился, поэтому, я думаю, именно поэтому сеттер не вызывается — но мне нужно, чтобы он был вызван, чтобы я мог вызвать CanExecuteChanged
4. Так как же элемент управления сообщает модели представления, что она изменилась?
5. И в этом-то и заключается моя проблема. Как мне это сделать? К вашему сведению, только что попробовал ввести сеттер Radius, SearchParams = новые параметры поиска(параметры поиска) (т. Е. Создать его новый экземпляр), и теперь вызывается мой сеттер параметров поиска в MainWindowViewModel (и я могу вызвать CanExecuteChanged там). Это определенно кажется банальным подходом, должен быть лучший способ.