我有多个扩展器,当我们扩展其中一个时,我正在寻找一种方法来折叠所有其他扩展器。我找到了这个解决方案here
<StackPanel Name="StackPanel1">
<StackPanel.Resources>
<local:ExpanderToBooleanConverter x:Key="ExpanderToBooleanConverter" />
</StackPanel.Resources>
<Expander Header="Expander 1"
IsExpanded="{Binding SelectedExpander, Mode=TwoWay, Converter={StaticResource ExpanderToBooleanConverter}, ConverterParameter=1}">
<TextBlock>Expander 1</TextBlock>
</Expander>
<Expander Header="Expander 2"
IsExpanded="{Binding SelectedExpander, Mode=TwoWay, Converter={StaticResource ExpanderToBooleanConverter}, ConverterParameter=2}">
<TextBlock>Expander 2</TextBlock>
</Expander>
<Expander Header="Expander 3"
IsExpanded="{Binding SelectedExpander, Mode=TwoWay, Converter={StaticResource ExpanderToBooleanConverter}, ConverterParameter=3}">
<TextBlock>Expander 3</TextBlock>
</Expander>
<Expander Header="Expander 4"
IsExpanded="{Binding SelectedExpander, Mode=TwoWay, Converter={StaticResource ExpanderToBooleanConverter}, ConverterParameter=4}">
<TextBlock>Expander 4</TextBlock>
</Expander>
</StackPanel>
public class ExpanderToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value == parameter);
// I tried thoses too :
return value != null && (value.ToString() == parameter.ToString());
return value != null && (value.ToString().Equals(parameter.ToString()));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return System.Convert.ToBoolean(value) ? parameter : null;
}
}
public class ExpanderListViewModel : INotifyPropertyChanged
{
private Object _selectedExpander;
public Object SelectedExpander
{
get { return _selectedExpander; }
set
{
if (_selectedExpander == value)
{
return;
}
_selectedExpander = value;
OnPropertyChanged("SelectedExpander");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
var viewModel = new ExpanderListViewModel();
StackPanel1.DataContext = viewModel;
viewModel.SelectedExpander = 1;
// I tried this also
viewModel.SelectedExpander = "1";
它工作正常,但现在我想在应用程序启动时扩展其中一个扩展器!
我已经尝试将值(1,2或3)放在 SelectedExpander 属性中,但默认情况下不会扩展任何扩展器!
如何将这种可能性添加到我的扩展器中?
答案 0 :(得分:6)
考虑如果在Expander 2上调用UpdateSource而选择Expander 1时会发生什么:
ConvertBack
为扩展器2调用其当前IsExpanded
值(false
),并返回null
。SelectedExpander
已更新为null
。Convert
,因为SelectedExpander
已更改,导致所有其他IsExpanded
值也设置为false
。当然,这不是正确的行为。所以解决方案依赖于源永远不会更新,除非用户实际切换扩展器。
因此,我怀疑问题是控件的初始化以某种方式触发源更新。即使扩展器1被正确初始化为扩展,当在任何其他扩展器上刷新绑定时,它也会被重置。
要使ConvertBack
更正,它需要知道其他扩展器:如果所有它们都被折叠,它应该只返回null
。但是,我没有看到从转换器中处理这个问题的干净方法。也许最好的解决方案是使用单向绑定(无ConvertBack
)并以这种方式或类似方式处理Expanded和Collapsed事件(其中_expanders
是所有扩展器控件的列表):
private void OnExpanderIsExpandedChanged(object sender, RoutedEventArgs e) {
var selectedExpander = _expanders.FirstOrDefault(e => e.IsExpanded);
if (selectedExpander == null) {
viewmodel.SelectedExpander = null;
} else {
viewmodel.SelectedExpander = selectedExpander.Tag;
}
}
在这种情况下,我使用Tag作为viewmodel中使用的标识符。
修改强>
要以更“MVVM”的方式解决它,您可以为每个扩展器设置一组视图模型,并使用单独的属性将IsExpanded
绑定到:
public class ExpanderViewModel {
public bool IsSelected { get; set; }
// todo INotifyPropertyChanged etc.
}
将集合存储在ExpanderListViewModel
中,并在初始化时为每个集合添加PropertyChanged处理程序:
// in ExpanderListViewModel
foreach (var expanderViewModel in Expanders) {
expanderViewModel.PropertyChanged += Expander_PropertyChanged;
}
...
private void Expander_PropertyChanged(object sender, PropertyChangedEventArgs e) {
var thisExpander = (ExpanderViewModel)sender;
if (e.PropertyName == "IsSelected") {
if (thisExpander.IsSelected) {
foreach (var otherExpander in Expanders.Except(new[] {thisExpander})) {
otherExpander.IsSelected = false;
}
}
}
}
然后将每个扩展器绑定到Expanders
集合的不同项:
<Expander Header="Expander 1" IsExpanded="{Binding Expanders[0].IsSelected}">
<TextBlock>Expander 1</TextBlock>
</Expander>
<Expander Header="Expander 2" IsExpanded="{Binding Expanders[1].IsSelected}">
<TextBlock>Expander 2</TextBlock>
</Expander>
(您可能还想研究定义自定义ItemsControl以根据集合动态生成扩展器。)
在这种情况下,将不再需要SelectedExpander
属性,但可以通过这种方式实现:
private ExpanderViewModel _selectedExpander;
public ExpanderViewModel SelectedExpander
{
get { return _selectedExpander; }
set
{
if (_selectedExpander == value)
{
return;
}
// deselect old expander
if (_selectedExpander != null) {
_selectedExpander.IsSelected = false;
}
_selectedExpander = value;
// select new expander
if (_selectedExpander != null) {
_selectedExpander.IsSelected = true;
}
OnPropertyChanged("SelectedExpander");
}
}
并将上述PropertyChanged处理程序更新为:
if (thisExpander.IsSelected) {
...
SelectedExpander = thisExpander;
} else {
SelectedExpander = null;
}
所以现在这两行是初始化第一个扩展器的等效方法:
viewModel.SelectedExpander = viewModel.Expanders[0];
viewModel.Expanders[0].IsSelected = true;
答案 1 :(得分:0)
更改Convert方法(给定here)内容如下
if (value == null)
return false;
return (value.ToString() == parameter.ToString());
由于与==运算符进行对象比较,以前的内容无效。
答案 2 :(得分:0)
我只使用您的代码创建了一个WPF项目,StackPanel
作为MainWindow
的内容,并在调用InitializeComponent()
之后调用初始化代码MainWindow()
只需移除
return (value == parameter);
来自ExpanderToBooleanConverter.Convert
的。实际上@Boopesh answer也有效。即使你这样做
return ((string)value == (string)parameter);
可行,但在这种情况下,SelectedExpander
仅支持字符串值。
我建议你再次尝试Convert
中的其他回复,如果它不起作用,你的问题可能在你的初始化代码中。在组件正确初始化之前,您可能正在设置SelectedExpander
。
答案 3 :(得分:0)
我写了一个示例代码,演示了如何实现你想要的东西。
<ItemsControl ItemsSource="{Binding Path=Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="group">
<RadioButton.Template>
<ControlTemplate>
<Expander Header="{Binding Path=Header}" Content="{Binding Path=Content}"
IsExpanded="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=IsChecked}" />
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
模型如下:
public class Model
{
public string Header { get; set; }
public string Content { get; set; }
}
ViewModel将模型公开给视图:
public IList<Model> Items
{
get
{
IList<Model> items = new List<Model>();
items.Add(new Model() { Header = "Header 1", Content = "Header 1 content" });
items.Add(new Model() { Header = "Header 2", Content = "Header 2 content" });
items.Add(new Model() { Header = "Header 3", Content = "Header 3 content" });
return items;
}
}
如果您不想创建视图模型(也许这是静态的),您可以使用x:Array标记扩展。
您可以找到示例here
答案 4 :(得分:0)
您需要在视图Loaded
之后设置属性<Window x:Class="UniformWindow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:UniformWindow"
Title="MainWindow" Loaded="Window_Loaded">
<!- your XAMLSnipped goes here->
</Window>
public partial class MainWindow : Window
{
ExpanderListViewModel vm = new ExpanderListViewModel();
public MainWindow()
{
InitializeComponent();
StackPanel1.DataContext = vm;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
vm.SelectedExpander = "2";
}
}
public class ExpanderToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// to prevent NullRef
if (value == null || parameter == null)
return false;
var sValue = value.ToString();
var sparam = parameter.ToString();
return (sValue == sparam);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (System.Convert.ToBoolean(value)) return parameter;
return null;
}
}
答案 5 :(得分:0)
我这样做了
<StackPanel Name="StackPanel1">
<Expander Header="Expander 1" Expanded="Expander_Expanded">
<TextBlock>Expander 1</TextBlock>
</Expander>
<Expander Header="Expander 2" Expanded="Expander_Expanded">
<TextBlock>Expander 2</TextBlock>
</Expander>
<Expander Header="Expander 3" Expanded="Expander_Expanded" >
<TextBlock>Expander 3</TextBlock>
</Expander>
<Expander Header="Expander 4" Expanded="Expander_Expanded" >
<TextBlock>Expander 4</TextBlock>
</Expander>
</StackPanel>
private void Expander_Expanded(object sender, RoutedEventArgs e)
{
foreach (Expander exp in StackPanel1.Children)
{
if (exp != sender)
{
exp.IsExpanded = false;
}
}
}