WPF ComboBox 在一个 DataTrigger 之后不显示 SelectedItem 但会显示另一个
WPF ComboBox Doesn't Display SelectedItem after one DataTrigger but does for another
所以我有一个组合框,我想将其重复用于多组数据,而不是拥有 3 个单独的组合框。也许这很糟糕,有人可以告诉我。我愿意接受所有的想法和建议。我只是想清理一些代码,并认为一个组合框比 3 个组合框更干净。无论如何,当另一个 ComboBox's
值发生变化时,ItemsSource
和 SelectedItem
都应该发生变化,这会引发无法正常工作的 ComboBox 的 属性 已更改值。最糟糕的是,当 CurSetpoint.ActLowerModeIsTimerCondition
为真时,它总是正确加载 SelectedItem
,但是当从那里到 CurSetpoint.ActLowerGseMode
为真时,组合框没有加载 SelectedItem
。
这是有问题的组合框的 XAML。
<ComboBox Grid.Row="1" Grid.Column="1" Margin="5,2" VerticalAlignment="Center" Name="cmbActTimersSetpointsGseVars">
<ComboBox.Style>
<Style BasedOn="{StaticResource {x:Type ComboBox}}" TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurSetpoint.ActLowerModeIsTimerCondition}" Value="True">
<Setter Property="ItemsSource" Value="{Binding TimerInstances}" />
<Setter Property="SelectedItem" Value="{Binding CurSetpoint.ActLowerTimerInstance, Mode=TwoWay}" />
<Setter Property="DisplayMemberPath" Value="DisplayName"></Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurSetpoint.ActLowerGseMode}" Value="True">
<Setter Property="ItemsSource" Value="{Binding EnabledGseVars}" />
<Setter Property="SelectedItem" Value="{Binding CurSetpoint.ActLowerGseVar, Mode=TwoWay}" />
<Setter Property="DisplayMemberPath" Value="DisplayName"></Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurSetpoint.ActModeIsLogicCondition}" Value="True">
<Setter Property="ItemsSource" Value="{Binding SetpointStates}" />
<Setter Property="SelectedItem" Value="{Binding CurSetpoint.ActSetpoint1State, Mode=TwoWay}" />
<Setter Property="DisplayMemberPath" Value="Value"></Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurSetpoint.ShowActLowerCmbBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
这是两个组合框的图像。当模式从 Timer 更改为 Variable 时,它不会加载任何内容,尽管它的绑定不是 null 并且它的实例和 itemssource 实例数据没有改变。但是如果我从 Variable 转到 Timer,Timer: 1 会正确显示。
这是正在更改的模式组合框值背后的模型代码。连同其他两个属性,即相关 ComboBox 的 SelectedItems
。连同 ItemsSource
的属性
private VarItem actLowerMode;
public VarItem ActLowerMode
{
get { return this.actLowerMode; }
set
{
if (value != null)
{
var oldValue = this.actLowerMode;
this.actLowerMode = value;
config.ActLowerMode.Value = value.ID;
//if they weren't the same we need to reset the variable name
//Note: 5/21/19 Needs to be this way instead of before because when changing from Timer->GseVariable it wouldn't change the config value because it
//thought it was still a timer condition because the value hadn't been changed yet.
if (oldValue != null && (oldValue.CheckAttribute("timer") != value.CheckAttribute("timer")))
{
if (value.CheckAttribute("timer"))
{
ActLowerTimerInstance = model.TimerInstances.First();
}
else
{
ActLowerVarName = "";
if (GseMode)
{
ActLowerGseVar = model.EnabledGseVars.FirstOrDefault();
}
}
}
RaisePropertyChanged("ActLowerMode");
RaisePropertyChanged("HasActLowerScale");
RaisePropertyChanged("ActLowerGseMode");
RaisePropertyChanged("HasActLowerVarName");
RaisePropertyChanged("ActLowerModeIsConstant");
RaisePropertyChanged("ActLowerRow1Label");
RaisePropertyChanged("ActLowerModeIsTimerCondition");
RaisePropertyChanged("ShowActLowerConstTextBox");
RaisePropertyChanged("ShowActLowerCmbBox");
RaisePropertyChanged("ShowActLowerRow1Label");
if (GseMode)
{
RaisePropertyChanged("ActLowerGseMode");
}
}
}
}
private GseVariableModel actLowerGseVar;
public GseVariableModel ActLowerGseVar
{
get { return this.actLowerGseVar; }
set
{
if (value != null)
{
this.actLowerGseVar = value;
if (!ActLowerModeIsTimerCondition)//only changing the config value if its not set to timer
{
config.ActLowerVarName.Value = value.Number.ToString();
}
RaisePropertyChanged("ActLowerGseVar");
}
}
}
private INumberedSelection actLowerTimerInstance;
public INumberedSelection ActLowerTimerInstance
{
get { return this.actLowerTimerInstance; }
set
{
if (value != null)
{
this.actLowerTimerInstance = value;
config.ActLowerVarName.Value = value.Number.ToString();
RaisePropertyChanged("ActLowerTimerInstance");
}
}
}
public ObservableCollection<INumberedSelection> TimerInstances { get { return this.timerInstances; } }
public ObservableCollection<GseVariableModel> EnabledGseVars
{
get
{
return enabledGseVariables;
}
}
我确定我可能忽略了一些重要信息,所以我会根据你们的任何问题或需要的详细信息进行更新。
更新:只是想按照赏金中的说明添加。如果我在这里做的不是一个好主意并且有更好的方法,请有经验的人告诉我为什么以及我应该怎么做。如果有更好的方法,而我做的不好,我只需要知道。
绑定多个 ComboBox
并设置它们的 Visibility
没有错。一方面,与 post.
中的代码相比,它大大降低了复杂性
尽管如此,您可以通过在视图模型之间引入额外的抽象来轻松交换 ItemsControl
的 context(不要与 DataContext
混淆)和风景。
这是它的工作原理:
- 创建具有相关属性的 context 对象
- 将上下文应用到您的
ItemsControl
- 让 de 属性在 context 上重新绑定已更改
您收集每个实体的属性的想法当然是个好主意。尽管实现可能会更好,但 viewmodel 和 view 看起来都很臃肿。这就是这个上下文对象的全部意义所在,在您来回交换上下文时收集和保持状态。
从我们的模型开始 classes。让我们针对接口进行编码(即使 ItemsSource 是无类型的)。
namespace WpfApp.Models
{
public interface IEntity
{
string Name { get; }
}
public class Dog : IEntity
{
public Dog(string breed, string name)
{
Breed = breed;
Name = name;
}
public string Breed { get; }
public string Name { get; }
}
public class Author : IEntity
{
public Author(string genre, string name)
{
Genre = genre;
Name = name;
}
public string Genre { get; }
public string Name { get; }
}
}
接下来是 ViewModel,从我们的上下文开始。
namespace WpfApp.ViewModels
{
public class ItemsContext : ViewModelBase
{
public ItemsContext(IEnumerable<IEntity> items)
{
if (items == null || !items.Any()) throw new ArgumentException(nameof(Items));
Items = new ObservableCollection<IEntity>(items);
SelectedItem = Items.First();
}
public ObservableCollection<IEntity> Items { get; }
private IEntity selectedItem;
public IEntity SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged();
}
}
public string DisplayMemberPath { get; set; }
}
}
如前所述,相关属性,带有 SelectedItem
的通知,没什么特别的。我们立即看到对 MainViewModel
.
的影响
namespace WpfApp.ViewModels
{
public class MainViewModel : ViewModelBase
{
private readonly ItemsContext _dogContext;
private readonly ItemsContext _authorContext;
public MainViewModel()
{
_dogContext = new ItemsContext(FetchDogs()) { DisplayMemberPath = nameof(Dog.Breed) };
_authorContext = new ItemsContext(FetchAuthors()) { DisplayMemberPath = nameof(Author.Genre) };
}
private ItemsContext selectedContext;
public ItemsContext SelectedContext
{
get { return selectedContext; }
set
{
selectedContext = value;
OnPropertyChanged();
}
}
private bool dogChecked;
public bool DogChecked
{
get { return dogChecked; }
set
{
dogChecked = value;
if(dogChecked) SelectedContext = _dogContext;
}
}
private bool authorChecked;
public bool AuthorChecked
{
get { return authorChecked; }
set
{
authorChecked = value;
if(authorChecked) SelectedContext = _authorContext;
}
}
private static IEnumerable<IEntity> FetchDogs() =>
new List<IEntity>
{
new Dog("Terrier", "Ralph"),
new Dog("Beagle", "Eddy"),
new Dog("Poodle", "Fifi")
};
private static IEnumerable<IEntity> FetchAuthors() =>
new List<IEntity>
{
new Author("SciFi", "Bradbury"),
new Author("RomCom", "James")
};
}
}
两个完全分离的流,每个流管理自己的上下文。很明显,您可以轻松地将其扩展到任意数量的上下文,而不会相互妨碍。现在,要将上下文应用到我们的 ItemsControl
我们有两个选择。我们可以 subclass 我们的 Control
或使用附加的 属性。支持组合而不是继承,这里是 class 和 AP。
namespace WpfApp.Extensions
{
public class Selector
{
public static ItemsContext GetContext(DependencyObject obj) => (ItemsContext)obj.GetValue(ContextProperty);
public static void SetContext(DependencyObject obj, ItemsContext value) => obj.SetValue(ContextProperty, value);
public static readonly DependencyProperty ContextProperty =
DependencyProperty.RegisterAttached("Context", typeof(ItemsContext), typeof(Selector), new PropertyMetadata(null, OnItemsContextChanged));
private static void OnItemsContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (System.Windows.Controls.Primitives.Selector)d;
var ctx = (ItemsContext)e.NewValue;
if (e.OldValue != null) // Clean up bindings from previous context, if any
{
BindingOperations.ClearBinding(selector, System.Windows.Controls.Primitives.Selector.SelectedItemProperty);
BindingOperations.ClearBinding(selector, ItemsControl.ItemsSourceProperty);
BindingOperations.ClearBinding(selector, ItemsControl.DisplayMemberPathProperty);
}
selector.SetBinding(System.Windows.Controls.Primitives.Selector.SelectedItemProperty, new Binding(nameof(ItemsContext.SelectedItem)) { Source = ctx, Mode = BindingMode.TwoWay });
selector.SetBinding(ItemsControl.ItemsSourceProperty, new Binding(nameof(ItemsContext.Items)) { Source = ctx });
selector.SetBinding(ItemsControl.DisplayMemberPathProperty, new Binding(nameof(ItemsContext.DisplayMemberPath)) { Source = ctx });
}
}
}
这涵盖了第 2 步和第 3 步。您可以根据需要对其进行调整。例如,我们已将 ItemsContext.DisplayMemberPath
设为非通知属性,因此您可以直接设置值而不是通过绑定。
最后,视图,所有这些都汇集在一起。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:WpfApp.ViewModels"
xmlns:ext="clr-namespace:WpfApp.Extensions"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Style x:Key="SelectorStyle" TargetType="{x:Type Selector}">
<Setter Property="Width" Value="150"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="0,20"/>
</Style>
</Window.Resources>
<StackPanel Margin="20">
<RadioButton GroupName="Entities" Content="Dogs" IsChecked="{Binding DogChecked}" />
<RadioButton GroupName="Entities" Content="Authors" IsChecked="{Binding AuthorChecked}" />
<ComboBox ext:Selector.Context="{Binding SelectedContext}" Style="{StaticResource SelectorStyle}" />
<ListBox ext:Selector.Context="{Binding SelectedContext}" Style="{StaticResource SelectorStyle}" />
<DataGrid ext:Selector.Context="{Binding SelectedContext}" Style="{StaticResource SelectorStyle}" />
</StackPanel>
</Window>
Attached 属性 最酷的地方在于我们针对抽象 Selector
控件进行编码,它是 ItemsControl
的直接后代。因此,在不更改我们的较低层的情况下,我们也可以与 ListBox
和 DataGrid
共享我们的上下文。
所以我有一个组合框,我想将其重复用于多组数据,而不是拥有 3 个单独的组合框。也许这很糟糕,有人可以告诉我。我愿意接受所有的想法和建议。我只是想清理一些代码,并认为一个组合框比 3 个组合框更干净。无论如何,当另一个 ComboBox's
值发生变化时,ItemsSource
和 SelectedItem
都应该发生变化,这会引发无法正常工作的 ComboBox 的 属性 已更改值。最糟糕的是,当 CurSetpoint.ActLowerModeIsTimerCondition
为真时,它总是正确加载 SelectedItem
,但是当从那里到 CurSetpoint.ActLowerGseMode
为真时,组合框没有加载 SelectedItem
。
这是有问题的组合框的 XAML。
<ComboBox Grid.Row="1" Grid.Column="1" Margin="5,2" VerticalAlignment="Center" Name="cmbActTimersSetpointsGseVars">
<ComboBox.Style>
<Style BasedOn="{StaticResource {x:Type ComboBox}}" TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurSetpoint.ActLowerModeIsTimerCondition}" Value="True">
<Setter Property="ItemsSource" Value="{Binding TimerInstances}" />
<Setter Property="SelectedItem" Value="{Binding CurSetpoint.ActLowerTimerInstance, Mode=TwoWay}" />
<Setter Property="DisplayMemberPath" Value="DisplayName"></Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurSetpoint.ActLowerGseMode}" Value="True">
<Setter Property="ItemsSource" Value="{Binding EnabledGseVars}" />
<Setter Property="SelectedItem" Value="{Binding CurSetpoint.ActLowerGseVar, Mode=TwoWay}" />
<Setter Property="DisplayMemberPath" Value="DisplayName"></Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurSetpoint.ActModeIsLogicCondition}" Value="True">
<Setter Property="ItemsSource" Value="{Binding SetpointStates}" />
<Setter Property="SelectedItem" Value="{Binding CurSetpoint.ActSetpoint1State, Mode=TwoWay}" />
<Setter Property="DisplayMemberPath" Value="Value"></Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurSetpoint.ShowActLowerCmbBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
这是两个组合框的图像。当模式从 Timer 更改为 Variable 时,它不会加载任何内容,尽管它的绑定不是 null 并且它的实例和 itemssource 实例数据没有改变。但是如果我从 Variable 转到 Timer,Timer: 1 会正确显示。
这是正在更改的模式组合框值背后的模型代码。连同其他两个属性,即相关 ComboBox 的 SelectedItems
。连同 ItemsSource
private VarItem actLowerMode;
public VarItem ActLowerMode
{
get { return this.actLowerMode; }
set
{
if (value != null)
{
var oldValue = this.actLowerMode;
this.actLowerMode = value;
config.ActLowerMode.Value = value.ID;
//if they weren't the same we need to reset the variable name
//Note: 5/21/19 Needs to be this way instead of before because when changing from Timer->GseVariable it wouldn't change the config value because it
//thought it was still a timer condition because the value hadn't been changed yet.
if (oldValue != null && (oldValue.CheckAttribute("timer") != value.CheckAttribute("timer")))
{
if (value.CheckAttribute("timer"))
{
ActLowerTimerInstance = model.TimerInstances.First();
}
else
{
ActLowerVarName = "";
if (GseMode)
{
ActLowerGseVar = model.EnabledGseVars.FirstOrDefault();
}
}
}
RaisePropertyChanged("ActLowerMode");
RaisePropertyChanged("HasActLowerScale");
RaisePropertyChanged("ActLowerGseMode");
RaisePropertyChanged("HasActLowerVarName");
RaisePropertyChanged("ActLowerModeIsConstant");
RaisePropertyChanged("ActLowerRow1Label");
RaisePropertyChanged("ActLowerModeIsTimerCondition");
RaisePropertyChanged("ShowActLowerConstTextBox");
RaisePropertyChanged("ShowActLowerCmbBox");
RaisePropertyChanged("ShowActLowerRow1Label");
if (GseMode)
{
RaisePropertyChanged("ActLowerGseMode");
}
}
}
}
private GseVariableModel actLowerGseVar;
public GseVariableModel ActLowerGseVar
{
get { return this.actLowerGseVar; }
set
{
if (value != null)
{
this.actLowerGseVar = value;
if (!ActLowerModeIsTimerCondition)//only changing the config value if its not set to timer
{
config.ActLowerVarName.Value = value.Number.ToString();
}
RaisePropertyChanged("ActLowerGseVar");
}
}
}
private INumberedSelection actLowerTimerInstance;
public INumberedSelection ActLowerTimerInstance
{
get { return this.actLowerTimerInstance; }
set
{
if (value != null)
{
this.actLowerTimerInstance = value;
config.ActLowerVarName.Value = value.Number.ToString();
RaisePropertyChanged("ActLowerTimerInstance");
}
}
}
public ObservableCollection<INumberedSelection> TimerInstances { get { return this.timerInstances; } }
public ObservableCollection<GseVariableModel> EnabledGseVars
{
get
{
return enabledGseVariables;
}
}
我确定我可能忽略了一些重要信息,所以我会根据你们的任何问题或需要的详细信息进行更新。
更新:只是想按照赏金中的说明添加。如果我在这里做的不是一个好主意并且有更好的方法,请有经验的人告诉我为什么以及我应该怎么做。如果有更好的方法,而我做的不好,我只需要知道。
绑定多个 ComboBox
并设置它们的 Visibility
没有错。一方面,与 post.
尽管如此,您可以通过在视图模型之间引入额外的抽象来轻松交换 ItemsControl
的 context(不要与 DataContext
混淆)和风景。
这是它的工作原理:
- 创建具有相关属性的 context 对象
- 将上下文应用到您的
ItemsControl
- 让 de 属性在 context 上重新绑定已更改
您收集每个实体的属性的想法当然是个好主意。尽管实现可能会更好,但 viewmodel 和 view 看起来都很臃肿。这就是这个上下文对象的全部意义所在,在您来回交换上下文时收集和保持状态。
从我们的模型开始 classes。让我们针对接口进行编码(即使 ItemsSource 是无类型的)。
namespace WpfApp.Models
{
public interface IEntity
{
string Name { get; }
}
public class Dog : IEntity
{
public Dog(string breed, string name)
{
Breed = breed;
Name = name;
}
public string Breed { get; }
public string Name { get; }
}
public class Author : IEntity
{
public Author(string genre, string name)
{
Genre = genre;
Name = name;
}
public string Genre { get; }
public string Name { get; }
}
}
接下来是 ViewModel,从我们的上下文开始。
namespace WpfApp.ViewModels
{
public class ItemsContext : ViewModelBase
{
public ItemsContext(IEnumerable<IEntity> items)
{
if (items == null || !items.Any()) throw new ArgumentException(nameof(Items));
Items = new ObservableCollection<IEntity>(items);
SelectedItem = Items.First();
}
public ObservableCollection<IEntity> Items { get; }
private IEntity selectedItem;
public IEntity SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged();
}
}
public string DisplayMemberPath { get; set; }
}
}
如前所述,相关属性,带有 SelectedItem
的通知,没什么特别的。我们立即看到对 MainViewModel
.
namespace WpfApp.ViewModels
{
public class MainViewModel : ViewModelBase
{
private readonly ItemsContext _dogContext;
private readonly ItemsContext _authorContext;
public MainViewModel()
{
_dogContext = new ItemsContext(FetchDogs()) { DisplayMemberPath = nameof(Dog.Breed) };
_authorContext = new ItemsContext(FetchAuthors()) { DisplayMemberPath = nameof(Author.Genre) };
}
private ItemsContext selectedContext;
public ItemsContext SelectedContext
{
get { return selectedContext; }
set
{
selectedContext = value;
OnPropertyChanged();
}
}
private bool dogChecked;
public bool DogChecked
{
get { return dogChecked; }
set
{
dogChecked = value;
if(dogChecked) SelectedContext = _dogContext;
}
}
private bool authorChecked;
public bool AuthorChecked
{
get { return authorChecked; }
set
{
authorChecked = value;
if(authorChecked) SelectedContext = _authorContext;
}
}
private static IEnumerable<IEntity> FetchDogs() =>
new List<IEntity>
{
new Dog("Terrier", "Ralph"),
new Dog("Beagle", "Eddy"),
new Dog("Poodle", "Fifi")
};
private static IEnumerable<IEntity> FetchAuthors() =>
new List<IEntity>
{
new Author("SciFi", "Bradbury"),
new Author("RomCom", "James")
};
}
}
两个完全分离的流,每个流管理自己的上下文。很明显,您可以轻松地将其扩展到任意数量的上下文,而不会相互妨碍。现在,要将上下文应用到我们的 ItemsControl
我们有两个选择。我们可以 subclass 我们的 Control
或使用附加的 属性。支持组合而不是继承,这里是 class 和 AP。
namespace WpfApp.Extensions
{
public class Selector
{
public static ItemsContext GetContext(DependencyObject obj) => (ItemsContext)obj.GetValue(ContextProperty);
public static void SetContext(DependencyObject obj, ItemsContext value) => obj.SetValue(ContextProperty, value);
public static readonly DependencyProperty ContextProperty =
DependencyProperty.RegisterAttached("Context", typeof(ItemsContext), typeof(Selector), new PropertyMetadata(null, OnItemsContextChanged));
private static void OnItemsContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (System.Windows.Controls.Primitives.Selector)d;
var ctx = (ItemsContext)e.NewValue;
if (e.OldValue != null) // Clean up bindings from previous context, if any
{
BindingOperations.ClearBinding(selector, System.Windows.Controls.Primitives.Selector.SelectedItemProperty);
BindingOperations.ClearBinding(selector, ItemsControl.ItemsSourceProperty);
BindingOperations.ClearBinding(selector, ItemsControl.DisplayMemberPathProperty);
}
selector.SetBinding(System.Windows.Controls.Primitives.Selector.SelectedItemProperty, new Binding(nameof(ItemsContext.SelectedItem)) { Source = ctx, Mode = BindingMode.TwoWay });
selector.SetBinding(ItemsControl.ItemsSourceProperty, new Binding(nameof(ItemsContext.Items)) { Source = ctx });
selector.SetBinding(ItemsControl.DisplayMemberPathProperty, new Binding(nameof(ItemsContext.DisplayMemberPath)) { Source = ctx });
}
}
}
这涵盖了第 2 步和第 3 步。您可以根据需要对其进行调整。例如,我们已将 ItemsContext.DisplayMemberPath
设为非通知属性,因此您可以直接设置值而不是通过绑定。
最后,视图,所有这些都汇集在一起。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:WpfApp.ViewModels"
xmlns:ext="clr-namespace:WpfApp.Extensions"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Style x:Key="SelectorStyle" TargetType="{x:Type Selector}">
<Setter Property="Width" Value="150"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="0,20"/>
</Style>
</Window.Resources>
<StackPanel Margin="20">
<RadioButton GroupName="Entities" Content="Dogs" IsChecked="{Binding DogChecked}" />
<RadioButton GroupName="Entities" Content="Authors" IsChecked="{Binding AuthorChecked}" />
<ComboBox ext:Selector.Context="{Binding SelectedContext}" Style="{StaticResource SelectorStyle}" />
<ListBox ext:Selector.Context="{Binding SelectedContext}" Style="{StaticResource SelectorStyle}" />
<DataGrid ext:Selector.Context="{Binding SelectedContext}" Style="{StaticResource SelectorStyle}" />
</StackPanel>
</Window>
Attached 属性 最酷的地方在于我们针对抽象 Selector
控件进行编码,它是 ItemsControl
的直接后代。因此,在不更改我们的较低层的情况下,我们也可以与 ListBox
和 DataGrid
共享我们的上下文。