绑定到用户控件中的依赖属性并在嵌套属性更改时调用 CanExecute
Posted
技术标签:
【中文标题】绑定到用户控件中的依赖属性并在嵌套属性更改时调用 CanExecute【英文标题】:Binding to dependencyproperty in user control and calling CanExecute on nested property change 【发布时间】:2021-12-30 08:27:18 【问题描述】:我创建了一个用户控件,它基本上包含一堆组合框,称为 SearchParamsControl。 SearchParamsControl 包含在 SearchParams 类中设置所有内容所需的所有 UI 元素:
我的 SearchParamsControl:
/// <summary>
/// Interaction logic for SearchParamsControl.xaml
/// </summary>
public partial class SearchParamsControl : INotifyPropertyChanged
public SearchParamsControl()
InitializeComponent();
/// <summary>
/// Radius entries bound to combobox
/// </summary>
public Dictionary<double, string> RadiusEntries
get;
set;
= new Dictionary<double, string>()
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 Dictionary<PropertyTypeEnum, string> PropertyTypes => PropertyTypeDictionary;
/// <summary>
/// Prices bound to combo box
/// </summary>
public List<int> Prices
get;
set;
= new List<int>()
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
;
/// <summary>
/// Bedrooms bound to combobox
/// </summary>
public List<int> Bedrooms
get;
set;
= new List<int>()
0,
1,
2,
3,
4,
5
;
public StringTrieSet SearchString
get
return RightMoveCodes.RegionTree;
public double Radius
get => 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:
<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">
<UserControl.DataContext>
<viewModel:SearchParamsControlViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues" ObjectType="x:Type System:Enum">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="dataTypes:SortType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<valueconverters:PropertyTypeConverter x:Key="PropertyTypeConverter" x:Shared="False"/>
<Style TargetType="x:Type ComboBox" BasedOn="StaticResource ComboStyle">
<Setter Property="Margin" Value="0,0,0,1"/>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="Search area"/>
<local:AutoCompleteComboBox Grid.Row="0" Grid.Column="1"
ItemsSource="Binding ElementName=uc, Path=SearchString"
SelectedValue="Binding Path=RegionLocation, Mode=TwoWay"/>
<Label Grid.Column="0" Grid.Row="1" Content="Search radius"/>
<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">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Binding Value"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Column="0" Grid.Row="2" Content="Price range (£)"/>
<Grid Grid.Column="1" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" Name="comboMinPrice"
ItemsSource="Binding ElementName=uc, Path=Prices"
SelectedItem="Binding ElementName=uc, Path=MinPrice, Mode=TwoWay">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Binding"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Column="1" Grid.Row="0" Content="to"/>
<ComboBox Grid.Column="2" Grid.Row="0" Name="comboMaxPrice"
ItemsSource="Binding ElementName=uc, Path=Prices"
SelectedItem="Binding ElementName=uc, Path=MaxPrice">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Binding"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<Label Grid.Column="0" Grid.Row="3" Content="No. of bedrooms"/>
<Grid Grid.Column="1" Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" Name="comboMinBedrooms"
ItemsSource="Binding ElementName=uc, Path=Bedrooms"
SelectedItem="Binding ElementName=uc, Path=MinBedrooms, Mode=TwoWay">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Binding"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Column="3" Grid.Row="0" Name="comboMaxBedrooms"
ItemsSource="Binding ElementName=uc, Path=Bedrooms"
SelectedItem="Binding ElementName=uc, Path=MaxBedrooms, Mode=TwoWay">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Binding"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Column="1" Grid.Row="0" Content="to"/>
</Grid>
<Label Grid.Column="0" Grid.Row="4" Content="Sort Type"/>
<ComboBox Grid.Column="1" Grid.Row="4" Name="comboSort"
ItemsSource="Binding Source=StaticResource dataFromEnum"
SelectedItem="Binding ElementName=uc, Path=SortType, Mode=TwoWay">
</ComboBox>
</Grid>
我想绑定到我的 MainWindow 中的 SearchParams 依赖属性,其中包含 SearchParamsControl:
<GroupBox Grid.Column="0" Grid.Row="0" Header="Search Params" Panel.ZIndex="10">
<controls:SearchParamsControl x:Name="searchControl"
SearchParams="Binding Path=SearchParams, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged"
IsEnabled="Binding Path=IsSearching, Converter=StaticResource BooleanToReverseConverter">
</controls:SearchParamsControl>
</GroupBox>
<Button x:Name="btnSearch" Grid.Column="0" Grid.Row="1"
Content="Search"
IsDefault="True"
Command="Binding SearchAsyncCommand"/>
此绑定工作正常。但是,如您所见,我的 MainWindow 中有一个 Button,它连接到一个命令。我想知道 SearchParams 中的属性何时发生更改,以便我可以调用类似 SearchCommandAsync.RaiseCanExecuteChanged() 的内容,以便更新“搜索”按钮的状态。我该如何处理?
请注意,我不想使用以下注释掉的代码(来自我的 Command 类),我认为它确实有效,但我希望能够通知 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 事件处理程序修改 SearchParams(想象它是一个封闭的类库,我不能修改它)。
【问题讨论】:
【参考方案1】:如果我正确理解您的设置,则视图上SearchParamsControl
的SearchParams
属性应在控件属性更改时设置视图模型的数据绑定SearchParams
源属性。
然后您可以在SearchParams
源属性的设置器中引发命令的CanExecuteChanged
方法。
【讨论】:
就是这样,SearchParams 源属性(如果你的意思是我的 MainWindow 的 MainWindowViewModel 中的 SearchParams)设置器永远不会执行,很可能是因为它是 SearchParams.Radius,比如说,它被改变了。但是,如果我强制 CanExecute 为 true,我可以在调试器中检查 MainViewModel 的 SearchParams 并确认 SearchParams 的 Radius 属性已正确更新,但永远不会命中 SearchParams 设置器断点。我什至尝试将 SearchParams=SearchParams 放在 UserControl 中的 Radius 设置中,以尝试强制调用 MainViewModel 中的设置器 所以绑定不起作用?顺便说一句,您应该从控件中删除<UserControl.DataContext>
元素。控件应该从父元素继承其DataContext
。
抱歉,我已将其删除(我已完全删除 SearchControlViewModel)。绑定确实有效,SearchParams 的修改属性在 MainWindowViewModel 中是正确的。当我在我的 ExecuteSearch 方法中放置一个断点时,我会调试 SearchParams 并且我的更改会正确显示。但是 SearchParams 本身并没有改变,所以我想这就是为什么不调用 setter 的原因 - 但我需要调用它,这样我才能引发 CanExecuteChanged
那么控件是如何告诉视图模型它已经改变的呢?
这就是我的问题所在。我该怎么做?仅供参考,刚刚尝试放入 Radius 的设置器 SearchParams = new SearchParams(SearchParams) (即创建它的新实例),现在调用 MainWindowViewModel 中的 SearchParams 设置器(我可以在那里提出 CanExecuteChanged )。这绝对看起来是一种 hacky 方法,一定有更好的方法。以上是关于绑定到用户控件中的依赖属性并在嵌套属性更改时调用 CanExecute的主要内容,如果未能解决你的问题,请参考以下文章