默认情况下折叠所有扩展器并扩展其中之一 [英] Collapse all the expanders and expand one of them by default

查看:57
本文介绍了默认情况下折叠所有扩展器并扩展其中之一的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有多个扩展器,我一直在寻找一种方法,当其中一个扩展器被扩展时,可以折叠所有其他扩展器.我在here中找到了这个解决方案>

XAML:

I have multiple expanders, and I was looking for a way to collapse all others the expanders when one of them is expanded. And I found this solution 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";

<小时>

它工作正常,但现在我想在应用程序启动时扩展其中一个扩展器!


It's working fine, but now I want to expand one of the expanders at the application startup !

我已经尝试将值(1、2 或 3)放在 SelectedExpander 属性中,但默认情况下没有扩展器被扩展!

I already tried to put the values (1, 2 or 3) in SelectedExpander property, but none of expanders get expanded by default !

如何将这种可能性添加到我的扩展器中?

How can I add this possibility to my expanders ?

推荐答案

考虑如果你调用 UpdateSource 在扩展器 2 上,同时选择扩展器 1:

Consider what would happen if you called UpdateSource on Expander 2 while Expander 1 is selected:

  • ConvertBack 使用其当前的 IsExpanded 值 (false) 为扩展器 2 调用,并返回 null.
  • SelectedExpander 更新为 null.
  • Convert 为所有其他扩展器调用,因为 SelectedExpander 已更改,导致所有其他 IsExpanded 值设置为 false 也是.
  • ConvertBack is called for Expander 2 with its current IsExpanded value (false), and returns null.
  • SelectedExpander is updated to null.
  • Convert is called for all other expanders, because SelectedExpander changed, causing all the other IsExpanded values to be set to false as well.

这当然不是正确的行为.因此,解决方案依赖于永远不会更新的源,除非用户实际切换扩展器.

This isn't the correct behavior, of course. So the solution is dependent on the source never being updated except for when a user actually toggles an expander.

因此,我怀疑问题在于控件的初始化以某种方式触发了源更新.即使扩展器 1 被正确地初始化为扩展,它也会在任何其他扩展器上刷新绑定时重置.

Thus, I suspect the problem is that the initialization of the controls is somehow triggering a source update. Even if Expander 1 was correctly initialized as expanded, it would be reset when the bindings were refreshed on any of the other expanders.

为了使 ConvertBack 正确,它需要知道其他扩展器:如果 all 是它们,它应该只返回 null崩溃了.不过,我没有看到从转换器内部处理这个问题的干净方法.也许最好的解决方案是使用单向绑定(无 ConvertBack)并处理 扩展以这种方式或类似方式折叠事件(其中 _expanders 是所有扩展器控件的列表):

To make ConvertBack correct, it would need to be aware of the other expanders: It should only return null if all of them are collapsed. I don't see a clean way of handling this from within a converter, though. Perhaps the best solution then would be to use a one-way binding (no ConvertBack) and handle the Expanded and Collapsed events this way or similar (where _expanders is a list of all of the expander controls):

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;
    }
}

在这种情况下,我使用 标签用于视图模型中使用的标识符.

In this case I'm using Tag for the identifier used in the viewmodel.

要以更MVVM"的方式解决它,您可以为每个扩展器创建一组视图模型,并使用一个单独的属性将 IsExpanded 绑定到:

To solve it in a more "MVVM" way, you could have a collection of viewmodels for each expander, with an individual property to bind IsExpanded to:

public class ExpanderViewModel {
    public bool IsSelected { get; set; }
    // todo INotifyPropertyChanged etc.
}

将集合存储在 ExpanderListViewModel 中并在初始化时为每个添加 PropertyChanged 处理程序:

Store the collection in ExpanderListViewModel and add PropertyChanged handlers for each one at initialization:

// 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 集合的不同项目:

Then bind each expander to a different item of the Expanders collection:

<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 基于集合动态生成扩展器.)

(You may also want to look into defining a custom ItemsControl to dynamically generate the Expanders based on the collection.)

在这种情况下,将不再需要 SelectedExpander 属性,但可以这样实现:

In this case the SelectedExpander property would no longer be needed, but it could be implemented this way:

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 处理程序更新为:

And update the above PropertyChanged handler as:

if (thisExpander.IsSelected) {
    ...
    SelectedExpander = thisExpander;
} else {
    SelectedExpander = null;
}

所以现在这两行是初始化第一个扩展器的等效方法:

So now these two lines would be equivalent ways of initializing the first expander:

viewModel.SelectedExpander = viewModel.Expanders[0];
viewModel.Expanders[0].IsSelected = true;

这篇关于默认情况下折叠所有扩展器并扩展其中之一的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆