Привязать к ActualHeight Item ItemsControl

У меня два отдельных ItemsControlс, которые появляются рядом. ItemsControlпривязать к тому же ItemsSource, но они отображают данные по-разному.

Каждый элемент, отображаемый слева, скорее всего будет меньше, чем тот же элемент справа. Это вызывает проблему, потому что строки не будут выстроены в линию, поэтому мне нужен элемент слева, чтобы связать его с элементом справа.

ItemsControl        ItemsControl
|Item 1         |Item 1
|Item 2         |Item 2
|Item 3         |
|Item 4         |Item 3

Как вы можете видеть, пункт 2 справа больше, поэтому он нарушает выравнивание. Так что, если я смогу связать левый пункт 2 с правым пунктом 2 ActualHeight проблема будет решена. Как я могу сделать это в XAML?

Изменить: чтобы сделать вещи более сложными, ItemsControl справа нужно прокрутить справа налево, но оба ItemsControls нужно прокручивать вверх и вниз вместе. По сути, левый обеспечивает своего рода заголовок для элементов справа.

3 ответа

Решение

Продолжение ответа Джоби Джой

Вы не можете сделать прямой OneWayToSource Привязка в Xaml для свойства зависимостей ReadOnly ActualHeight, но есть много обходных путей. Ответ Кента Бугаарта в этом вопросе мой любимый. Что делает, так это то, что он использует присоединенное поведение, которое слушает SizeChanged событие любого FrameworkElement и обновляет два Прикрепленных свойства, Ширина и Высота, соответственно.

С TextBlock например, ActualHeight может быть использован для вставки в свойство высоты ViewModel, как

<TextBlock local:ActualSizeBehavior.ObserveActualSize="True"
           local:ActualSizeBehavior.ActualHeight="{Binding Path=Height,
                                                           Mode=OneWayToSource}"
           .../>

Синхронизировать два ScrollViewers
Вы можете использовать DependencyPropertyDescriptor прислушиваться к изменениям в VerticalOffsetProperty собственности или подписаться на ScrollChanged событие и звонок ScrollToVerticalOffset, пример

Xaml

<ScrollViewer Name="scrollViewerLeft"
              ScrollChanged="scrollViewerLeft_ScrollChanged">
<ScrollViewer Name="scrollViewerRight"
              ScrollChanged="scrollViewerRight_ScrollChanged">

Код за обработчиками событий

private void scrollViewerLeft_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    scrollViewerRight.ScrollToVerticalOffset(scrollViewerLeft.VerticalOffset);
}
private void scrollViewerRight_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    scrollViewerLeft.ScrollToVerticalOffset(scrollViewerRight.VerticalOffset);
}

ActualSizeBehavior

public static class ActualSizeBehavior
{
    public static readonly DependencyProperty ActualSizeProperty =
        DependencyProperty.RegisterAttached("ActualSize",
                                            typeof(bool),
                                            typeof(ActualSizeBehavior),
                                            new UIPropertyMetadata(false, OnActualSizeChanged));
    public static bool GetActualSize(DependencyObject obj)
    {
        return (bool)obj.GetValue(ActualSizeProperty);
    }
    public static void SetActualSize(DependencyObject obj, bool value)
    {
        obj.SetValue(ActualSizeProperty, value);
    }
    private static void OnActualSizeChanged(DependencyObject dpo,
                                            DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement element = dpo as FrameworkElement;
        if ((bool)e.NewValue == true)
        {
            element.SizeChanged += element_SizeChanged;
        }
        else
        {
            element.SizeChanged -= element_SizeChanged;
        }
    }

    static void element_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        FrameworkElement element = sender as FrameworkElement;
        SetActualWidth(element, element.ActualWidth);
        SetActualHeight(element, element.ActualHeight);
    }

    private static readonly DependencyProperty ActualWidthProperty =
        DependencyProperty.RegisterAttached("ActualWidth", typeof(double), typeof(ActualSizeBehavior));
    public static void SetActualWidth(DependencyObject element, double value)
    {
        element.SetValue(ActualWidthProperty, value);
    }
    public static double GetActualWidth(DependencyObject element)
    {
        return (double)element.GetValue(ActualWidthProperty);
    }

    private static readonly DependencyProperty ActualHeightProperty =
        DependencyProperty.RegisterAttached("ActualHeight", typeof(double), typeof(ActualSizeBehavior));
    public static void SetActualHeight(DependencyObject element, double value)
    {
        element.SetValue(ActualHeightProperty, value);
    }
    public static double GetActualHeight(DependencyObject element)
    {
        return (double)element.GetValue(ActualHeightProperty);
    }
}

Посмотрите на мою статью: http://www.codeproject.com/KB/WPF/BindingHub.aspx

Вот как вы можете связать со свойствами зависимости только для чтения, используя BindingHub:

<bindings:BindingHub 
       Visibility="Hidden"
       Socket1="{Binding ActualWidth, ElementName=Item, Mode=OneWay}"
       Socket2="{Binding ItemWidth, Mode=OneWayToSource}"
       Connect="(1 in, 2 out)"/>

Я должен был решить именно эту проблему. Я использовал найденное здесь решение привязки только для чтения:https://meleak.wordpress.com/2011/08/28/onewaytosource-binding-for-readonly-dependency-property/

Используя это, я смог привязать только чтение ActualHeight каждого ListViewItem в одном ListView к свойству в моей модели представления элементов, называемой ListViewItemHeight. Затем во втором ListView, Я привязал высоту каждого элемента к ListViewItemHeight.

Так как ItemsSource то же самое на обоих, вы можете использовать один ItemsControl и целый ряд, представленный в виде двух разделов (два столбца сетки) внутри этого единого DataTemplate, тогда высоты будут выровнены автоматически. Вы всегда можете придать ему стиль, чтобы он выглядел как часть двух разных ItemsControl но технически один.

Другой способ это добавить, добавив свойство Height в ViewModel (конечно, не очень правильный дизайн, так как добавление зависимости View в VM). TwoWay привязывает высоту к ActualHeight элемента left-itemsControl ItemContainerStyle. А в элементе управления right-items свяжите это свойство Height с высотой ItemsContainerStyle {One Way}. Таким образом, оба будут синхронизированы.

Еще одна идея, основанная на вашем обновлении "Необходимость прокрутки с правой стороны": используйте один ListView и имейте в нем два столбца, и из этих двух GridViewColumn.CellTemplate есть ваши два DataTemplates. Эта идея все еще нуждается в замораживании столбцов в первом столбце. Но это может быть более сложным.

Во всяком случае, я бы пошел с первым подходом здесь.

Другие вопросы по тегам