图像控件 ActualWidth 仅在第二次加载后更正 - Image Cropper

Image control ActualWidth correct only after second load - Image Cropper

我正在尝试从 CodeProject 扩展裁剪控制,能够从磁盘中选择图像,使用 Stretch='Uniform' 显示它,并能够根据纵横比调整裁剪区域的大小。

我已经完成了所有的修改,但是我有一个问题 - 我必须加载相同的图像两次才能获得图像控制的 ActualWidth

我已经在 SO (Why are ActualWidth and ActualHeight 0.0 in this case?) 上搜索了解决方案,但我无法让它工作。

下面是我的完整代码:

windows.xaml:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="CroppingTest.WndCroppingTest"
    Title="CroppingTest"
    Width="900" Height="600" Background="OliveDrab"
    SizeChanged="Window_SizeChanged" Loaded="WndCroppingTest_OnLoaded"
    >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
            <Grid>
                <Rectangle Fill="White">
                    <Rectangle.Effect>
                        <DropShadowEffect Opacity="0.5" />
                    </Rectangle.Effect>
                </Rectangle>
                <Image x:Name="Crop" Stretch="Uniform" VerticalAlignment="Center" HorizontalAlignment="Center" />
            </Grid>
        </Grid>
        <StackPanel HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Grid.Column="2">
            <StackPanel.Resources>
                <Style TargetType="CheckBox">
                    <Setter Property="Margin" Value="5,5,5,5"/>
                </Style>
            </StackPanel.Resources>
            <Image x:Name="Preview" Width="130" Height="100" Margin="0,5,5,0"/>
            <Button Content="Open" HorizontalAlignment="Stretch" Margin="0,10" Click="OnOpen"/>
            <Button Content="Save" HorizontalAlignment="Stretch" Margin="0,10" Click="OnSave"/>
        </StackPanel>
        <TextBlock HorizontalAlignment="Stretch" Margin="5,0,0,5" x:Name="tblkClippingRectangle" VerticalAlignment="Top" Width="Auto" Height="Auto" Grid.Row="1" Foreground="#FFFFFFFF" Text="ClippingRectangle" TextWrapping="Wrap"/>
    </Grid>
</Window>

隐藏代码:

using System;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media.Imaging;
using DAP.Adorners;
using Microsoft.Win32;

namespace CroppingTest
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>

    public partial class WndCroppingTest
    {
        CroppingAdorner _clp;
        FrameworkElement _felCur;

        public WndCroppingTest()
        {
            InitializeComponent();
        }

        private string _s;

        public WndCroppingTest(string source)
        {
            _s = source;
            InitializeComponent();

        }

        private void RemoveCropFromCur()
        {
            AdornerLayer aly = AdornerLayer.GetAdornerLayer(_felCur);
            aly.Remove(_clp);
        }

        private void AddCropToImage(Image fel)
        {
            if (_felCur != null)
            {
                RemoveCropFromCur();
            }

            Size s = new Size(80,120);
            double ratio = s.Width/s.Height;
            Rect r = new Rect();
            if (ratio < 1)
            {
                r.Height = fel.ActualHeight;
                r.Width = fel.ActualHeight*ratio;
                r.Y = 0;
                r.X = (fel.ActualWidth - r.Width)/2;
            }
            else
            {
                r.Width = fel.ActualWidth;
                r.Height = fel.ActualWidth / ratio;
                r.X = 0;
                r.Y = (fel.ActualHeight - r.Height) / 2;
            }
            AdornerLayer aly = AdornerLayer.GetAdornerLayer(fel);
            _clp = new CroppingAdorner(fel, r,true);
            aly.Add(_clp);
            Preview.Source = _clp.BpsCrop();
            _clp.CropChanged += CropChanged;
            _felCur = fel;
        }

        private void RefreshCropImage()
        {
            if (_clp != null)
            {
                Rect rc = _clp.ClippingRectangle;

                tblkClippingRectangle.Text = string.Format(
                    "Clipping Rectangle: ({0:N1}, {1:N1}, {2:N1}, {3:N1})",
                    rc.Left,
                    rc.Top,
                    rc.Right,
                    rc.Bottom);
                Preview.Source = _clp.BpsCrop();
            }
        }

        private void CropChanged(Object sender, RoutedEventArgs rea)
        {
            RefreshCropImage();
        }

        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            RefreshCropImage();
        }

        private void OnOpen(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openfile = new OpenFileDialog
            {
                //Filter = "JPEG (*.jpeg)|*.jpeg|PNG (*.png)|*.png|JPG (*.jpg)|*.jpg"
                Filter = "Obrazy (*.jpeg, *.png, *.jpg)|*.jpeg;*.png;*.jpg"
            };
            bool? result = openfile.ShowDialog();
            if (result == true)
            {
                //MessageBox.Show(openfile.FileName);

                var source = openfile.FileName;
                Crop.Source= new BitmapImage(new Uri(source));
                AddCropToImage(Crop);
                RefreshCropImage();
            }
        }

        private void OnSave(object sender, RoutedEventArgs e)
        {
            SaveFileDialog dlg = new SaveFileDialog
            {
                FileName = "Avatar",
                DefaultExt = ".png",
                Filter = "PNGi (.png)|*.png"
            };
            bool? result = dlg.ShowDialog();
            if (result == true)
            {
                string filename = dlg.FileName;

                using (var fileStream = new FileStream(filename, FileMode.Create))
                {
                    BitmapEncoder encoder = new PngBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(_clp.BpsCrop()));
                    encoder.Save(fileStream);
                }
            }
        }

        private void WndCroppingTest_OnLoaded(object sender, RoutedEventArgs e)
        {
            if (_s != null)
            {
                Crop.Source = new BitmapImage(new Uri(_s));
                AddCropToImage(Crop);
                RefreshCropImage();
            }
        }
    }
}

裁剪装饰器:

using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Brush = System.Windows.Media.Brush;
using Brushes = System.Windows.Media.Brushes;
using Color = System.Windows.Media.Color;
using Image = System.Windows.Controls.Image;
using Pen = System.Windows.Media.Pen;
using Point = System.Drawing.Point;
using Size = System.Windows.Size;

namespace DAP.Adorners
{
    public class CroppingAdorner : Adorner
    {
        #region Private variables
        // Width of the thumbs.  I know these really aren't "pixels", but px
        // is still a good mnemonic.
        private const int _cpxThumbWidth = 6;

        // PuncturedRect to hold the "Cropping" portion of the adorner
        private PuncturedRect _prCropMask;

        // Canvas to hold the thumbs so they can be moved in response to the user
        private Canvas _cnvThumbs;

        // Cropping adorner uses Thumbs for visual elements.  
        // The Thumbs have built-in mouse input handling.
        private CropThumb _crtTopLeft, _crtTopRight, _crtBottomLeft, _crtBottomRight;
        //private CropThumb _crtTop, _crtLeft, _crtBottom, _crtRight;

        // To store and manage the adorner's visual children.
        private VisualCollection _vc;

        // DPI for screen
        private static double s_dpiX, s_dpiY;


        private Size _originalSize, _controlSize;

        private Image _i;

        private ImageSource _s;
        private BitmapImage _b;
        #endregion

        #region Properties
        public Rect ClippingRectangle
        {
            get
            {
                return _prCropMask.RectInterior;
            }
        }
        #endregion

        #region Routed Events
        public static readonly RoutedEvent CropChangedEvent = EventManager.RegisterRoutedEvent(
            "CropChanged",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(CroppingAdorner));

        public event RoutedEventHandler CropChanged
        {
            add
            {
                AddHandler(CropChangedEvent, value);
            }
            remove
            {
                RemoveHandler(CropChangedEvent, value);
            }
        }
        #endregion

        #region Dependency Properties
        static public DependencyProperty FillProperty = Shape.FillProperty.AddOwner(typeof(CroppingAdorner));

        public Brush Fill
        {
            get { return (Brush)GetValue(FillProperty); }
            set { SetValue(FillProperty, value); }
        }

        private static void FillPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            CroppingAdorner crp = d as CroppingAdorner;

            if (crp != null)
            {
                crp._prCropMask.Fill = (Brush)args.NewValue;
            }
        }
        #endregion

        #region Constructor
        static CroppingAdorner()
        {
            Color clr = Colors.Black;
            Graphics g = Graphics.FromHwnd((IntPtr)0);

            s_dpiX = g.DpiX;
            s_dpiY = g.DpiY;
            clr.A = 80;
            FillProperty.OverrideMetadata(typeof(CroppingAdorner),
                new PropertyMetadata(
                    new SolidColorBrush(clr),
                    FillPropChanged));
        }

        public CroppingAdorner(Image sourceImage, Rect rcInit, bool fixedRatio = false)
            : base(sourceImage)
        {
            _fixedRatio = fixedRatio;
            _ratio = rcInit.Width/rcInit.Height;
            _i = sourceImage;
            _s = sourceImage.Source;
            try
            {
                _b = (BitmapImage) sourceImage.Source;
            }
            catch (Exception e)
            {
                Debug.WriteLine(e);
            }

            try
            {
                _originalSize = new Size(_b.PixelWidth, _b.PixelHeight);
            }
            catch (Exception e)
            {
                _originalSize = new Size(1,1);
            }
            _controlSize = new Size(sourceImage.ActualWidth, sourceImage.ActualHeight);
            _vc = new VisualCollection(this);
            _prCropMask = new PuncturedRect();
            _prCropMask.IsHitTestVisible = false;
            _prCropMask.RectInterior = rcInit;
            _prCropMask.Fill = Fill;
            _vc.Add(_prCropMask);
            _cnvThumbs = new Canvas();
            _cnvThumbs.HorizontalAlignment = HorizontalAlignment.Stretch;
            _cnvThumbs.VerticalAlignment = VerticalAlignment.Stretch;

            _vc.Add(_cnvThumbs);
            //BuildCorner(ref _crtTop, Cursors.SizeNS);
            //BuildCorner(ref _crtBottom, Cursors.SizeNS);
            //BuildCorner(ref _crtLeft, Cursors.SizeWE);
            //BuildCorner(ref _crtRight, Cursors.SizeWE);
            BuildCorner(ref _crtTopLeft, Cursors.SizeNWSE);
            BuildCorner(ref _crtTopRight, Cursors.SizeNESW);
            BuildCorner(ref _crtBottomLeft, Cursors.SizeNESW);
            BuildCorner(ref _crtBottomRight, Cursors.SizeNWSE);

            // Add handlers for Cropping.
            _crtBottomLeft.DragDelta += HandleBottomLeft;
            _crtBottomRight.DragDelta += HandleBottomRight;
            _crtTopLeft.DragDelta += HandleTopLeft;
            _crtTopRight.DragDelta += HandleTopRight;
            //_crtTop.DragDelta += HandleTop;
            //_crtBottom.DragDelta += HandleBottom;
            //_crtRight.DragDelta += HandleRight;
            //_crtLeft.DragDelta += HandleLeft;

            //add eventhandler to drag and drop 
            sourceImage.MouseLeftButtonDown += Handle_MouseLeftButtonDown;
            sourceImage.MouseLeftButtonUp += Handle_MouseLeftButtonUp;
            sourceImage.MouseMove += Handle_MouseMove;

            // We have to keep the clipping interior withing the bounds of the adorned element
            // so we have to track it's size to guarantee that...
            FrameworkElement fel = sourceImage;
            fel.SizeChanged += AdornedElement_SizeChanged;
        }
        #endregion

        #region Drag and drop handlers

        Double OrigenX;
        Double OrigenY;
        private readonly bool _fixedRatio;
        private double _ratio;

        //  generic handler move selection with Drag'n'Drop
        private void HandleDrag(double dx, double dy)
        {
            Rect rcInterior = _prCropMask.RectInterior;
            rcInterior = new Rect(
               dx,
               dy,
                rcInterior.Width,
                rcInterior.Height);

            _prCropMask.RectInterior = rcInterior;
            SetThumbs(_prCropMask.RectInterior);
            RaiseEvent(new RoutedEventArgs(CropChangedEvent, this));
        }

        private void Handle_MouseMove(object sender, MouseEventArgs args)
        {
            Image Marco = sender as Image;
            if (Marco != null && Marco.IsMouseCaptured)
            {
                Double x = args.GetPosition(Marco).X; //posición actual cursor
                Double y = args.GetPosition(Marco).Y;
                Double _x = _prCropMask.RectInterior.X; // posición actual esquina superior izq del marco interior
                Double _y = _prCropMask.RectInterior.Y;
                Double _width = _prCropMask.RectInterior.Width; //dimensiones del marco interior
                Double _height = _prCropMask.RectInterior.Height;

                //si el click es dentro del marco interior
                if (((x > _x) && (x < (_x + _width))) && ((y > _y) && (y < (_y + _height))))
                {
                    //calculamos la diferencia de la posición actual del cursor con respecto al punto de origen del arrastre
                    //y se la añadimos a la esquina sup. izq. del marco interior.
                    _x = _x + (x - OrigenX);
                    _y = _y + (y - OrigenY);

                    //comprobamos si es posible mover sin salirse del marco exterior por ninguna de sus dimensiones
                    //no supera el borde izquierdo de la imagen: !(_x < 0)
                    if (_x < 0)
                    {
                        _x = 0;
                    }
                    //no supera el borde derecho de la imagen: !((_x + _width) > Marco.Width)
                    if ((_x + _width) > Marco.ActualWidth)
                    {
                        _x = Marco.ActualWidth - _width;
                    }
                    //no supera el borde superior de la imagen: !(_y<0)
                    if (_y < 0)
                    {
                        _y = 0;
                    }
                    //no supera el borde inferior de la imagen: !((_y + _height) > Marco.Height)
                    if ((_y + _height) > Marco.ActualHeight)
                    {
                        _y = Marco.ActualHeight - _height;
                    }

                    //asignamos nuevo punto origen del arrastre y movemos el marco interior
                    OrigenX = x;
                    OrigenY = y;
                    HandleDrag(_x, _y);

                }
            }

        }

        private void Handle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Image Marco = sender as Image;
            if (Marco != null)
            {
                Marco.CaptureMouse();
                OrigenX = e.GetPosition(Marco).X; //iniciamos las variables en el punto de origen del arrastre
                OrigenY = e.GetPosition(Marco).Y;
            }
        }

        private void Handle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            Image Marco = sender as Image;
            if (Marco != null)
            {
                Marco.ReleaseMouseCapture();
            }
        }
        #endregion

        #region Thumb handlers
        // Generic handler for Cropping
        private void HandleThumb(
            double drcL,
            double drcT,
            double drcW,
            double drcH,
            double dx,
            double dy)
        {
            Rect rcInterior = _prCropMask.RectInterior;

            if (rcInterior.Width + drcW * dx < 0)
            {
                dx = -rcInterior.Width / drcW;
            }

            if (rcInterior.Height + drcH * dy < 0)
            {
                dy = -rcInterior.Height / drcH;
            }

            rcInterior = new Rect(
                rcInterior.Left + drcL * dx,
                rcInterior.Top + drcT * dy,
                rcInterior.Width + drcW * dx,
                rcInterior.Height + drcH * dy);

            if (_fixedRatio)
            {
                if (_ratio < 1)
                {
                    if (rcInterior.Height > _i.ActualHeight)
                    {
                        rcInterior.Height = _i.ActualHeight;
                    }
                    rcInterior.Width = rcInterior.Height * _ratio;
                }
                else
                {
                    if (rcInterior.Width > _i.ActualWidth)
                    {
                        rcInterior.Width = _i.ActualWidth;
                    }
                    rcInterior.Height = rcInterior.Width / _ratio;
                }
            }

            _prCropMask.RectInterior = rcInterior;
            SetThumbs(_prCropMask.RectInterior);
            RaiseEvent( new RoutedEventArgs(CropChangedEvent, this));
        }

        // Handler for Cropping from the bottom-left.
        private void HandleBottomLeft(object sender, DragDeltaEventArgs args)
        {
            if (sender is CropThumb)
            {
                HandleThumb(
                    1, 0, -1, 1,
                    args.HorizontalChange,
                    args.VerticalChange);
            }
        }

        // Handler for Cropping from the bottom-right.
        private void HandleBottomRight(object sender, DragDeltaEventArgs args)
        {
            if (sender is CropThumb)
            {
                HandleThumb(
                    0, 0, 1, 1,
                    args.HorizontalChange,
                    args.VerticalChange);
            }
        }

        // Handler for Cropping from the top-right.
        private void HandleTopRight(object sender, DragDeltaEventArgs args)
        {
            if (sender is CropThumb)
            {
                HandleThumb(
                    0, 1, 1, -1,
                    args.HorizontalChange,
                    args.VerticalChange);
            }
        }

        // Handler for Cropping from the top-left.
        private void HandleTopLeft(object sender, DragDeltaEventArgs args)
        {
            if (sender is CropThumb)
            {
                HandleThumb(
                    1, 1, -1, -1,
                    args.HorizontalChange,
                    args.VerticalChange);
            }
        }

        #endregion

        #region Other handlers
        private void AdornedElement_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            FrameworkElement fel = sender as FrameworkElement;
            Rect rcInterior = _prCropMask.RectInterior;
            bool fFixupRequired = false;
            double
                intLeft = rcInterior.Left,
                intTop = rcInterior.Top,
                intWidth = rcInterior.Width,
                intHeight = rcInterior.Height;

            if (rcInterior.Left > fel.RenderSize.Width)
            {
                intLeft = fel.RenderSize.Width;
                intWidth = 0;
                fFixupRequired = true;
            }

            if (rcInterior.Top > fel.RenderSize.Height)
            {
                intTop = fel.RenderSize.Height;
                intHeight = 0;
                fFixupRequired = true;
            }

            if (rcInterior.Right > fel.RenderSize.Width)
            {
                intWidth = Math.Max(0, fel.RenderSize.Width - intLeft);
                fFixupRequired = true;
            }

            if (rcInterior.Bottom > fel.RenderSize.Height)
            {
                intHeight = Math.Max(0, fel.RenderSize.Height - intTop);
                fFixupRequired = true;
            }
            if (fFixupRequired)
            {
                _prCropMask.RectInterior = new Rect(intLeft, intTop, intWidth, intHeight);
            }
        }
        #endregion

        #region Arranging/positioning
        private void SetThumbs(Rect rc)
        {
            _crtBottomRight.SetPos(rc.Right, rc.Bottom);
            _crtTopLeft.SetPos(rc.Left, rc.Top);
            _crtTopRight.SetPos(rc.Right, rc.Top);
            _crtBottomLeft.SetPos(rc.Left, rc.Bottom);
            //_crtTop.SetPos(rc.Left + rc.Width / 2, rc.Top);
            //_crtBottom.SetPos(rc.Left + rc.Width / 2, rc.Bottom);
            //_crtLeft.SetPos(rc.Left, rc.Top + rc.Height / 2);
            //_crtRight.SetPos(rc.Right, rc.Top + rc.Height / 2);
        }

        // Arrange the Adorners.
        protected override Size ArrangeOverride(Size finalSize)
        {
            Rect rcExterior = new Rect(0, 0, AdornedElement.RenderSize.Width, AdornedElement.RenderSize.Height);
            _prCropMask.RectExterior = rcExterior;
            Rect rcInterior = _prCropMask.RectInterior;
            _prCropMask.Arrange(rcExterior);

            SetThumbs(rcInterior);
            _cnvThumbs.Arrange(rcExterior);
            return finalSize;
        }
        #endregion

        #region Public interface
        public BitmapSource BpsCrop()
        {
            Thickness margin = AdornerMargin();
            Rect rcInterior = _prCropMask.RectInterior;

            Point pxFromSize = UnitsToPx(rcInterior.Width, rcInterior.Height);

            // It appears that CroppedBitmap indexes from the upper left of the margin whereas RenderTargetBitmap renders the
            // control exclusive of the margin.  Hence our need to take the margins into account here...

            Point pxFromPos = UnitsToPx(rcInterior.Left, rcInterior.Top);
            Point pxWhole = UnitsToPx(AdornedElement.RenderSize.Width, AdornedElement.RenderSize.Height);
            pxFromSize.X = Math.Max(Math.Min(pxWhole.X - pxFromPos.X, pxFromSize.X), 0);
            pxFromSize.Y = Math.Max(Math.Min(pxWhole.Y - pxFromPos.Y, pxFromSize.Y), 0);
            if (pxFromSize.X == 0 || pxFromSize.Y == 0)
            {
                return null;
            }

            var Width = _i.ActualWidth;
            var Height = _i.ActualHeight;

            int x = (int)(rcInterior.Left * _originalSize.Width / Width);
            int y = (int)(rcInterior.Top * _originalSize.Height / Height);

            int xx = (int)((rcInterior.Width) * _originalSize.Width / Width);
            int yy = (int)((rcInterior.Height) * _originalSize.Height / Height);
            Int32Rect rcFrom = new Int32Rect(x, y, xx, yy);

            //Int32Rect rcFrom = new Int32Rect(pxFromPos.X, pxFromPos.Y, pxFromSize.X, pxFromSize.Y);

            RenderTargetBitmap rtb = new RenderTargetBitmap(pxWhole.X, pxWhole.Y, s_dpiX, s_dpiY, PixelFormats.Default);
            rtb.Render(AdornedElement);

            try
            {
                return new CroppedBitmap(_b, rcFrom);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e);
                return new CroppedBitmap(rtb, new Int32Rect(0,0, 100,100));
            }

        }

        public static Size RelativeSize(double aspectRatio)
        {
            return (aspectRatio > 1)
                ? new Size(1, 1 / aspectRatio)
                : new Size(aspectRatio, 1);
        }
        #endregion

        #region Helper functions
        private Thickness AdornerMargin()
        {
            Thickness thick = new Thickness(0);
            if (AdornedElement is FrameworkElement)
            {
                thick = ((FrameworkElement)AdornedElement).Margin;
            }
            return thick;
        }

        private void BuildCorner(ref CropThumb crt, Cursor crs)
        {
            if (crt != null) return;

            crt = new CropThumb(_cpxThumbWidth);

            // Set some arbitrary visual characteristics.
            crt.Cursor = crs;

            _cnvThumbs.Children.Add(crt);
        }

        private Point UnitsToPx(double x, double y)
        {
            return new Point((int)(x * s_dpiX / 96), (int)(y * s_dpiY / 96));
        }
        #endregion

        #region Visual tree overrides
        // Override the VisualChildrenCount and GetVisualChild properties to interface with 
        // the adorner's visual collection.
        protected override int VisualChildrenCount { get { return _vc.Count; } }
        protected override Visual GetVisualChild(int index) { return _vc[index]; }
        #endregion

        #region Internal Classes
        class CropThumb : Thumb
        {
            #region Private variables
            int _cpx;
            #endregion

            #region Constructor
            internal CropThumb(int cpx)
                : base()
            {
                _cpx = cpx;
            }
            #endregion

            #region Overrides
            protected override Visual GetVisualChild(int index)
            {
                return null;
            }

            protected override void OnRender(DrawingContext drawingContext)
            {
                drawingContext.DrawRoundedRectangle(Brushes.White, new Pen(Brushes.Black, 1), new Rect(new Size(_cpx, _cpx)), 1, 1);
            }
            #endregion

            #region Positioning
            internal void SetPos(double x, double y)
            {
                Canvas.SetTop(this, y - _cpx / 2);
                Canvas.SetLeft(this, x - _cpx / 2);
            }
            #endregion
        }
        #endregion
    }

}

PunctedRect(我不能在这里包含代码,因为它超过了问题长度限制,抱歉添加link)

我正在尝试创建的是可在 Win7 上运行的裁剪工具,并允许我 select 具有纵横比的图像部分。

正如我之前所写,我曾尝试解决 ActualWidth 问题,但我无法解决。如何解决?

任何人都可以建议具有描述功能的替代(免费)控件吗?有很多 WUP(Windows 通用平台)应用程序和控件,但我需要 Win7 兼容。

在第一次访问 ActualWidth 之前尝试调用 UpdateLayout。

this.UpdateLayout();

来自MSDN

When you call this method, elements with IsMeasureValid false or IsArrangeValid false will call element-specific MeasureCore and ArrangeCore methods, which forces layout update, and all computed sizes will be validated.

[...] You should only call UpdateLayout if you absolutely need updated sizes and positions, and only after you are certain that all changes to properties that you control and that may affect layout are completed.