Как свернуть/развернуть несколько элементов управления Expander в зависимости от имени группы в WPF?

Чего я хочу добиться: свернуть или развернуть все расширители, которые имеют одно и то же имя группы в стиле группы.

У нас есть ListBox и еще один ListBox, вложенный в него для отображения дочерних элементов. Дочерние элементы ItemsSource привязаны к CollectionView с прикрепленными описаниями групп.

Шаблон элемента группы довольно прост:

<Expander IsExpanded="{Binding Path=WHAT_TO_DO, Mode=TwoWay}">
                <Expander.Header>
                    <TextBlock Text="{Binding Name}" />
                </Expander.Header>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" SharedSizeGroup="{Binding Name, Converter={StaticResource GroupNameToUniqueNameConverter}}" />
                    </Grid.RowDefinitions>
                    <ItemsPresenter/>
                </Grid>
            </Expander>

person David    schedule 02.07.2010    source источник
comment
Вы пытаетесь сделать это в стиле WPF?   -  person Gishu    schedule 02.07.2010
comment
Да, это GroupStyle ListBox с триггером стиля, который устанавливает свойство Template. Итак, это ControlTemplate с TargetType={x:Type GroupItem}.   -  person David    schedule 02.07.2010


Ответы (3)


Не думаю, что это можно сделать так легко. Тут наверное два варианта

1) Вручную - привязать каждый расширитель, который вам нужен, например. первый элемент в группе или что-нибудь еще, что подходит.

2) Более автоматический способ, вероятно, потребует написания класса, в котором есть словарь, содержащий состояния для каждой группы. Проблема здесь в том, что контроллеру нужно знать, какой экземпляр расширителя выполняет привязку или в какой группе он находится. Одно из решений, которое я здесь вижу, — это написать собственный преобразователь, который имеет статический экземпляр класса со словарем и использовать преобразователь. параметр для передачи группы/ссылки (здесь вы можете использовать привязку, поэтому в xaml это чистая операция Ctrl+C, Ctrl+V)

person kubal5003    schedule 02.07.2010
comment
На самом деле мы пытались найти решение, похожее на ваш второй вариант, но я не могу связать имя группы с обычным {Binding}, так как я думаю, что оно является динамическим для контекста данных? Мы попытались передать имя группы и словарь (в PM) конвертеру в мультипривязке, но были некоторые проблемы в методе обратного преобразования, поскольку мы не знаем имя группы. - person David; 02.07.2010
comment
Привязки работают и для динамических вещей, сейчас я не вижу проблемы. Позже я рассмотрю его подробнее и попытаюсь создать рабочий пример. - person kubal5003; 02.07.2010
comment
Извините, было немного непонятно. Невозможно использовать Binding для передачи динамического ConverterParameter в Converter. - person David; 02.07.2010

Вы можете в коде перебирать все элементы списка. Для каждого элемента списка вы должны увидеть, содержит ли он расширитель. Если это так, вы просто расширяете или сворачиваете его. См. эту ссылку, чтобы узнать, как перебирать элементы и находить определенный элемент управления.

Есть ли способ итерации в элементах ListBox шаблоны?

person Wallstreet Programmer    schedule 02.07.2010
comment
Худшее рабочее решение, потому что это общее решение, которое можно использовать повторно? Худшее рабочее решение, потому что оно обрабатывает вопросы, связанные с представлением, в представлении, не запутывая ваш контроллер кодом, специфичным для представления? - person Wallstreet Programmer; 02.07.2010

Создайте свой собственный элемент управления - GroupExpander - подкласс его от Expander

Используйте следующий вспомогательный метод, чтобы найти любого родителя в визуальной иерархии:

    public static IEnumerable<T> RecurseParents<T>(this DependencyObject child)
    {
        if (child is T)
        {
            yield return Get<T>(child);
        }

        if (child != null)
        {
            foreach (var parent in RecurseParents<T>(child.GetParentObject()))
            {
                yield return parent;
            }
        }
    }

    public static DependencyObject GetParentObject(this DependencyObject child)
    {
        if (child == null) return null;

        // handle content elements separately
        var contentElement = child as ContentElement;
        if (contentElement != null)
        {
            var parent = ContentOperations.GetParent(contentElement);
            if (parent != null) return parent;

            var fce = contentElement as FrameworkContentElement;
            return fce != null ? fce.Parent : null;
        }

        // also try searching for parent in framework elements (such as DockPanel, etc)
        var frameworkElement = child as FrameworkElement;
        if (frameworkElement != null)
        {
            var parent = frameworkElement.Parent;
            if (parent != null) return parent;
        }

        // if it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper
        return VisualTreeHelper.GetParent(child);
    }

теперь напишите логику поиска уровня GroupExpander, найдя родительский расширитель и вычислив, какой это расширитель

вы можете получить идею из следующего кода:

public class DataGridGroupExpander : GroupExpander
{
    #region Class level variables

    private bool mSettingIsExpanded = false;

    #endregion

    #region Constructors

    public DataGridGroupExpander()
    {
        SetBinding(HeaderProperty, new Binding("Name"));

        Loaded += DataGridGroupExpander_Loaded;
    }

    #endregion

    #region Properties

    #region Owner

    private DataGrid mOwner = null;
    public DataGrid Owner
    {
        get { return mOwner; }
        private set
        {
            if (mOwner != value)
            {
                if (mOwner != null)
                {
                    DetachOwner();
                }
                mOwner = value;
                if (mOwner != null)
                {
                    AttachOwner();
                }
            }
        }
    }

    private void AttachOwner()
    {
        SetFieldName();
    }

    private void DetachOwner()
    {

    }

    #endregion

    #region ParentExpander

    private DataGridGroupExpander mParentExpander = null;
    public DataGridGroupExpander ParentExpander
    {
        get { return mParentExpander; }
        private set
        {
            if (mParentExpander != value)
            {
                if (mParentExpander != null)
                {
                    DetachParentExpander();
                }
                mParentExpander = value;
                if (mParentExpander != null)
                {
                    AttachParentExpander();
                }
            }
        }
    }

    private void AttachParentExpander()
    {
        SetBinding(ParentExpanderLevelProperty, new Binding("Level") { Source = ParentExpander });
    }

    private void DetachParentExpander()
    {
        ClearValue(ParentExpanderLevelProperty);
    }

    #endregion

    #endregion

    #region Event handlers

    private void DataGridGroupExpander_Loaded(object sender, System.Windows.RoutedEventArgs e)
    {
        ParentExpander =  this.RecurseParents<DataGridGroupExpander>().Skip(1).Take(20).FirstOrDefault();
        Owner = this.RecurseParents<DataGrid>().FirstOrDefault();
        LoadGroupIsExpandedState();
    }

    #endregion

    #region Methods

    private void LoadGroupIsExpandedState()
    {
        if (!mSettingIsExpanded && Owner != null)
        {
            mSettingIsExpanded = true;
            try
            {
                IsExpanded = Owner.LoadGroupIsExpandedState(Header);
            }
            finally
            {
                mSettingIsExpanded = false;
            }
        }
    }

    private void PersistGroupIsExpandedState()
    {
        if (!mSettingIsExpanded && Owner != null)
        {
            Owner.PersistGroupIsExpandedState(Header, IsExpanded);
        }
    }

    private void SetFieldName()
    {
        var fieldName = "";
        if (Owner != null && Owner.Items != null && Owner.Items.GroupDescriptions.Count > Level)
        {
            var groupDescription = Owner.Items.GroupDescriptions[Level] as PropertyGroupDescription;
            if(groupDescription!=null)
            {
                fieldName = groupDescription.PropertyName;
            }
        }
        SetValue(FieldNameKey, fieldName);
    }

    #endregion

    #region Overrides

    tected override void HeaderUpdated()
    {
        LoadGroupIsExpandedState();
    }

    tected override void IsExpandedUpdated()
    {
        PersistGroupIsExpandedState();
    }

    #endregion

    #region Dependency Properties

    #region ParentExpanderLevel

    public int ParentExpanderLevel
    {
        get { return (int)GetValue(ParentExpanderLevelProperty); }
        set { SetValue(ParentExpanderLevelProperty, value); }
    }

    public static readonly System.Windows.DependencyProperty ParentExpanderLevelProperty =
        System.Windows.DependencyProperty.Register(
            "ParentExpanderLevel",
            typeof(int),
            typeof(DataGridGroupExpander),
            new System.Windows.PropertyMetadata(-1, OnParentExpanderLevelPropertyChanged));

    private static void OnParentExpanderLevelPropertyChanged(System.Windows.DependencyObject sender, System.Windows.DependencyPropertyChangedEventArgs e)
    {
        var control = sender as DataGridGroupExpander;
        if (control != null)
        {
            control.ParentExpanderLevelUpdated();
        }
    }

    private void ParentExpanderLevelUpdated()
    {
        SetValue(LevelKey, ParentExpanderLevel + 1);
    }

    #endregion

    #region Level

    public int Level
    {
        get { return (int)GetValue(LevelProperty); }
    }

    internal static readonly DependencyPropertyKey LevelKey = DependencyProperty.RegisterReadOnly(
          "Level",
          typeof(int),
          typeof(DataGridGroupExpander),
          new System.Windows.PropertyMetadata(0, OnLevelPropertyChanged));

    public static readonly DependencyProperty LevelProperty = LevelKey.DependencyProperty;

    private static void OnLevelPropertyChanged(System.Windows.DependencyObject sender, System.Windows.DependencyPropertyChangedEventArgs e)
    {
        var control = sender as DataGridGroupExpander;
        if (control != null)
        {
            control.LevelUpdated();
        }
    }

    private void LevelUpdated()
    {
        SetFieldName();
    }

    #endregion

    #region FieldName

    public string FieldName
    {
        get { return (string)GetValue(FieldNameProperty); }
    }

    internal static readonly DependencyPropertyKey FieldNameKey = DependencyProperty.RegisterReadOnly(
          "FieldName",
          typeof(string),
          typeof(DataGridGroupExpander),
          new PropertyMetadata(null, OnFieldNamePropertyChanged));

    public static readonly DependencyProperty FieldNameProperty = FieldNameKey.DependencyProperty;

    private static void OnFieldNamePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var control = sender as DataGridGroupExpander;
        if (control != null)
        {
            control.FieldNameUpdated();
        }
    }

    private void FieldNameUpdated()
    {

    }

    #endregion

    #endregion
}
person Kirill Chilingarashvili    schedule 08.09.2011