Bind to ActualHeight of Item ItemsControl

冷暖自知 提交于 2019-12-04 06:31:06
Fredrik Hedblad

Following up on Jobi Joy's answer

You can't do a direct OneWayToSource Binding in Xaml for the ReadOnly Dependency Property ActualHeight but there are many workarounds. The answer by Kent Boogaart in this question is my favorite. What is does is that it uses an Attached Behavior that listens to the SizeChanged event of any FrameworkElement and updates two Attached Properties, Width and Height, accordingly.

With a TextBlock for example, ActualHeight can be used to push into a Height property of the ViewModel like

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

Synkronize two ScrollViewers
You can either use a DependencyPropertyDescriptor to listen for changes in the VerticalOffsetProperty property or subscribe to the ScrollChanged event and call ScrollToVerticalOffset. Example

Xaml

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

Code behind event handlers

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);
    }
}
Jobi Joy

Since the ItemsSource is same on both, you can use a single ItemsControl and a whole row represented as two sections (Two columns of a Grid) inside that single DataTemplate, then heights will get aligned automatically. You can always style it to look like it is part of two different ItemsControl but technically one.

Another way to go with this is, adding a Height property in to the ViewModel (of course not very right design since adding View dependency to VM). TwoWay bind the height to the ActualHeight of the left-itemsControl ItemContainerStyle. And on the right-itemscontrol bind that Height property to the Height of ItemsContainerStyle {One Way}. So both will be in sync.

Another idea based on your update 'Need of the scroll on the right side' : Use a single ListView and have two columns in it, and of those two GridViewColumn.CellTemplate have your two DataTemplates. This idea still needs column freezing on the first column. But that may be more tricky.

Anyway I would go with the first approach here.

Take a look at my article: http://www.codeproject.com/KB/WPF/BindingHub.aspx

That's how you can bind to Read-Only Dependency Properties using BindingHub:

<bindings:BindingHub 
       Visibility="Hidden"
       Socket1="{Binding ActualWidth, ElementName=Item, Mode=OneWay}"
       Socket2="{Binding ItemWidth, Mode=OneWayToSource}"
       Connect="(1 in, 2 out)"/>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!