【问题标题】:WPF canvas and placing itemsWPF 画布和放置项目
【发布时间】:2012-12-05 12:08:43
【问题描述】:

我想要一个可以均匀拉伸的图像作为背景。然后我希望我的画布的大小等于均匀拉伸的图像。(我发现的一种解决方法是将图像和画布放在网格内,并将画布宽度/高度绑定到图像的实际高度/宽度)。

然后我想在画布内放置绝对定位的项目。但这是问题所在。如果项目 X 为 50 且图像宽度为 200 像素,则可以。但如果图像宽度为 100 像素,项目 X 应该是 25。我也想要一个小比例的变换。有没有办法通过绑定来实现这一点? 我是否必须实现我的自定义面板用户控件,因为我没有看到其他干净的解决方案。

【问题讨论】:

    标签: wpf canvas


    【解决方案1】:

    你当然可以通过绑定来实现这一切,但我想创建一个派生面板会更容易和更清晰,将子级安排在相对于面板的实际宽度和实际高度的位置。

    派生的 Panel 类可以定义子位置的附加属性,类似于 Canvas.Left 和 Canvas.Top,但小数值范围为 0 到 1。

    它还可以将背景图像作为依赖属性来维护。您将覆盖 OnRender 方法以直接绘制此图像。

    面板还可以通过除以计算当前比例因子,例如它的 ActualWidth 由图像的宽度。一个适当的ScaleTransform 属性可以很容易地附加到每个孩子的 RenderTransform。

    编辑:除了面板尺寸相对于背景图像的一致性之外,以下内容可能会让您了解如何实现该面板:

    public class ImagePanel : Panel
    {
        public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached(
            "Left", typeof(double), typeof(ImagePanel),
            new FrameworkPropertyMetadata(PositionChanged));
    
        public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached(
            "Top", typeof(double), typeof(ImagePanel),
            new FrameworkPropertyMetadata(PositionChanged));
    
        public static readonly DependencyProperty ImageProperty = DependencyProperty.Register(
            "Image", typeof(ImageSource), typeof(ImagePanel),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
    
        private ScaleTransform scaleTransform = new ScaleTransform();
    
        public static double GetLeft(DependencyObject obj)
        {
            return (double)obj.GetValue(LeftProperty);
        }
    
        public static void SetLeft(DependencyObject obj, double value)
        {
            obj.SetValue(LeftProperty, value);
        }
    
        public static double GetTop(DependencyObject obj)
        {
            return (double)obj.GetValue(TopProperty);
        }
    
        public static void SetTop(DependencyObject obj, double value)
        {
            obj.SetValue(TopProperty, value);
        }
    
        public ImageSource Image
        {
            get { return (ImageSource)GetValue(ImageProperty); }
            set { SetValue(ImageProperty, value); }
        }
    
        protected override void OnRender(DrawingContext dc)
        {
            if (Image != null)
            {
                dc.DrawImage(Image, new Rect(0d, 0d, ActualWidth, ActualHeight));
            }
    
            base.OnRender(dc);
        }
    
        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);
    
            if (Image != null)
            {
                scaleTransform.ScaleX = sizeInfo.NewSize.Width / Image.Width;
                scaleTransform.ScaleY = sizeInfo.NewSize.Height / Image.Height;
            }
        }
    
        protected override Size MeasureOverride(Size availableSize)
        {
            foreach (UIElement element in InternalChildren)
            {
                element.Measure(availableSize);
            }
    
            return new Size();
        }
    
        protected override Size ArrangeOverride(Size finalSize)
        {
            foreach (UIElement element in InternalChildren)
            {
                element.RenderTransform = scaleTransform;
                ArrangeElement(element, finalSize.Width, finalSize.Height);
            }
    
            return finalSize;
        }
    
        private static void PositionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var element = obj as UIElement;
            var panel = VisualTreeHelper.GetParent(obj) as ImagePanel;
    
            if (element != null && panel != null)
            {
                ArrangeElement(element, panel.ActualWidth, panel.ActualHeight);
            }
        }
    
        private static void ArrangeElement(UIElement element, double width, double height)
        {
            var left = GetLeft(element);
            var top = GetLeft(element);
            var rect = new Rect(new Point(left * width, top * height), element.DesiredSize);
    
            element.Arrange(rect);
        }
    }
    

    【讨论】:

    • 当前我确实以某种方式使用绑定,但它无缘无故地弄乱了模型。这似乎很有希望。我不知道如何定义附加属性,但我想我可以弄清楚。 0..1 似乎是个好主意,因为我可以让用户将元素放置在他想要的位置,而不必在开始时以全尺寸显示图像或其他任何东西。有没有办法缓存图像?因为否则我想要大约 4-5 个面板。另外,如果我将 scaletransform 放在每个孩子身上,它会如何考虑它的因素?我的意思是绑定还是通过面板中的代码?如果绑定意味着我的 POCO 有一个额外的属性?谢谢
    • “有没有办法缓存图像”:WPF 应该在内存中缓存位图,否则 System.Runtime.Caching.MemoryCache 将是一个选项。 “通过绑定或面板中的代码进行 ScaleTransform”:最简单的方法是简单地设置每个孩子的 RenderTransform 属性(例如在 Panel.ArrangeOverride 中),但当然孩子们不能定义自己的 RenderTransform。也应该可以绑定到 Parent.ScaleTransform 之类的东西,而无需任何额外的 viewmodel 属性。
    • 谢谢!好的,看来它有效。现在的问题是,使用附加属性作为位置我如何允许用户移动它?我使用拇指,但我应该以某种方式将 dX 移动映射到 0..1
    • 如果您需要移动项目,我想您最好使用像 Canvas.Left 和 Canvas.Top 这样的绝对坐标。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多