【问题标题】:WPF CustomControl loses binding when collapsedWPF CustomControl 在折叠时失去绑定
【发布时间】:2019-09-09 16:15:10
【问题描述】:

您好,当父控件 DockPanel 将其可见性设置为折叠时,我制作了这个未正确绑定的 XYController。隐藏时它工作正常。

ThumbPosXThumbPosY 应该移动,因为属性 SatVal 是从不同的地方控制的。但是当它回到 Visible 时,它​​们总是回到 [0, 0]。

public class XYController : Control
{
    static XYController()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(XYController), new FrameworkPropertyMetadata(typeof(XYController)));
        EventManager.RegisterClassHandler(typeof(XYController), Thumb.DragDeltaEvent, new DragDeltaEventHandler(XYController.OnThumbDragDelta));
    }

    #region Private Members
    private const string ThumbName = "PART_Thumb";
    private TranslateTransform m_thumbTransform = new TranslateTransform();
    private Thumb m_thumb;
    #endregion


    #region Dependency Properties
    public Color SelectedColor
    {
        get { return (Color)GetValue(SelectedColorProperty); }
        set { SetValue(SelectedColorProperty, value); }
    }
    public static readonly DependencyProperty SelectedColorProperty =
    DependencyProperty.Register("SelectedColor", typeof(Color), typeof(XYController),
        new PropertyMetadata(Colors.Transparent));


    public double ThumbPosX
    {
        get { return (double)GetValue(ThumbPosXProperty); }
        set { SetValue(ThumbPosXProperty, value); }
    }
    public static readonly DependencyProperty ThumbPosXProperty =
    DependencyProperty.Register("ThumbPosX", typeof(double), typeof(XYController),
        new PropertyMetadata(0.0, new PropertyChangedCallback(OnThumbPosChanged)));


    public double ThumbPosY
    {
        get { return (double)GetValue(ThumbPosYProperty); }
        set { SetValue(ThumbPosYProperty, value); }
    }
    public static readonly DependencyProperty ThumbPosYProperty =
    DependencyProperty.Register("ThumbPosY", typeof(double), typeof(XYController),
        new PropertyMetadata(0.0, new PropertyChangedCallback(OnThumbPosChanged)));


    private static void OnThumbPosChanged(DependencyObject relatedObject, DependencyPropertyChangedEventArgs e)
    {
        XYController xycontroller = relatedObject as XYController;
        if (xycontroller != null)
        {
            xycontroller.m_thumbTransform.X = xycontroller.ThumbPosX * xycontroller.ActualWidth;
            xycontroller.m_thumbTransform.Y = xycontroller.ThumbPosY * xycontroller.ActualHeight;
        }
    }
    #endregion

    private void UpdatePosition(double positionX, double positionY)
    {
        positionX = LimitValue(positionX, ActualWidth);
        positionY = LimitValue(positionY, ActualHeight);

        m_thumbTransform.X = positionX;
        m_thumbTransform.Y = positionY;

        ThumbPosX = map(positionX, 0, ActualWidth, 0, 1);
        ThumbPosY = map(positionY, 0, ActualHeight, 0, 1);
    }

    #region Event Handlers
    private void OnThumbDragDelta(DragDeltaEventArgs e)
    {
        double offsetX = m_thumbTransform.X + e.HorizontalChange;
        double offsetY = m_thumbTransform.Y + e.VerticalChange;
        UpdatePosition(offsetX, offsetY);
    }

    private static void OnThumbDragDelta(object sender, DragDeltaEventArgs e)
    {
        XYController hsvControl = sender as XYController;
        hsvControl.OnThumbDragDelta(e);
    }

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        if (m_thumb != null)
        {
            Point position = e.GetPosition(this);
            UpdatePosition(position.X, ActualHeight - position.Y); //Check canvas scale in style 
            m_thumb.RaiseEvent(e);
        }
        base.OnMouseLeftButtonDown(e);
    }

    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        m_thumbTransform.X = ThumbPosX * ActualWidth;
        m_thumbTransform.Y = ThumbPosY * ActualHeight;
        base.OnRenderSizeChanged(sizeInfo);
    }
    #endregion

    private double LimitValue(double value, double max)
    {
        if (value < 0)
            value = 0;
        if (value > max)
            value = max;
        return value;
    }

    private double map(double value, double fromLow, double fromHigh, double toLow, double toHigh)
    {
        return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        m_thumb = GetTemplateChild(ThumbName) as Thumb;
        if (m_thumb != null)
        {
            UpdatePosition(this.ThumbPosX, this.ThumbPosY);
            m_thumb.RenderTransform = m_thumbTransform;
        }
    }
}

所以我发布了整个内容,因为我不知道缺少什么。我还制作了自定义滑块,它们在折叠时可以正常工作。

我是这样用的:

    <DockPanel 
        Visibility="{Binding Path=IsChecked, ElementName=HueRadio, Converter={StaticResource BoolToVisibility}}">
        <local:ColorSlider
            Minimum="0"
            Maximum="360"
            Orientation="Vertical"
            Style="{DynamicResource HueSliderVertical}"
            Value="{Binding Hue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            Margin="0, 4, 16, 4"
            />
        <local:XYController
            x:Name="HueColorControl"
            Style="{StaticResource HueController}"
            SelectedColor="{Binding SelectedColor}"
            ThumbPosX="{Binding Sat, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            ThumbPosY="{Binding Val, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            Margin="0, 4" 
            Height="200" 
            />
    </DockPanel>

只是对RadioButton 的简单绑定。如前所述,即使DockPanel 折叠,也是CustomControl 的ColorSlider 仍在移动。

我尝试将可见性直接设置为 XYController 而不是 DockPanel,但问题仍然存在。

这里有什么遗漏吗?

谢谢

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

    您对位置值的计算涉及ActualHeightActualWidth 作为参数。当Visibility 设置为Visibility.Collapsed 时,这些值为0LimitValue 将始终返回 max 参数的值(即 0)。 OnThumbPosChanged 也会将 m_thumbTransform 设置为 0

    您必须检测折叠状态并立即停止计算:

    private static void OnThumbPosChanged(DependencyObject relatedObject, DependencyPropertyChangedEventArgs e)
    {
        if (this.Visibility == Visibility.Collapsed)
        {
           return
        }
    
        XYController xycontroller = relatedObject as XYController;
        if (xycontroller != null)
        {
            xycontroller.m_thumbTransform.X = xycontroller.ThumbPosX * xycontroller.ActualWidth;
            xycontroller.m_thumbTransform.Y = xycontroller.ThumbPosY * xycontroller.ActualHeight;
        }
    }
    

    private void UpdatePosition(double positionX, double positionY)
    {
        if (this.Visibility == Visibility.Collapsed)
        {
           return;
        }
    
        positionX = LimitValue(positionX, ActualWidth);
        positionY = LimitValue(positionY, ActualHeight);
    
        m_thumbTransform.X = positionX;
        m_thumbTransform.Y = positionY;
    
        ThumbPosX = map(positionX, 0, ActualWidth, 0, 1);
        ThumbPosY = map(positionY, 0, ActualHeight, 0, 1);
    }
    

    Visibility 设置回Visibility.Visible 将恢复折叠前的最后一个值。要立即更新位置值,您需要为 Visibility 属性注册一个 PropertyChanged 回调:

    static XYController()
    {
        UIElement.VisibilityProperty.OverrideMetadata(
        typeof(XYController),
        new PropertyMetadata(Visibility.Visible, OnVisibililtyChanged));
    
    
        DefaultStyleKeyProperty.OverrideMetadata(typeof(XYController), new FrameworkPropertyMetadata(typeof(XYController)));
        EventManager.RegisterClassHandler(typeof(XYController), Thumb.DragDeltaEvent, new DragDeltaEventHandler(XYController.OnThumbDragDelta));
    }
    
    private static void OnVisibililtyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      // Update control
      OnThumbPosChanged(d, null);
    
      // Do more updates
    }
    

    【讨论】:

      【解决方案2】:

      我认为将可见性设置为 Collapsed 会导致 ActualWidth 和 ActualHeight 设置为 0 的渲染。 这将导致您的 m_thumbTransform.X 和 m_thumbTransform.Y 设置为 0。然后,当您将 Visibility 设置回 Visible 时,您可能在 ApplyTemplate 发生时尚未完成渲染,因此 ActualWidth 和 ActualHeight 仍可能为 0。

      我认为问题是由于试图在 2 个不同的地方(m_thumbTransform 和依赖属性)保持相同的概念(X 和 Y 坐标对)。

      解决方案是消除 2 个状态中的 1 个,这样您就不必尝试保持信息同步,而只需拥有 1 个事实来源。

      您能否将转换作为单个依赖属性并使用绑定转换器来设置转换的各个组件? 例如,您可以将 Sat 映射到 X,将 Val 映射到 Y,但也可以根据组件的 Width 和 Height 设置 Scale 组件。 也可能值得尝试使用 Width、Height 以及 MeasuredWidth 和 MeasuredHeight。由于 ActualWidth 和 ActualHeight 仅在渲染周期后设置。

      【讨论】:

        猜你喜欢
        • 2013-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-16
        • 1970-01-01
        相关资源
        最近更新 更多