0

I'm trying to extend crop control from CodeProject with ability to choose image from disk, display it with Stretch='Uniform' and ability to resize crop area with aspect ratio.

I've done all the modifications, but I have a problem - i must load same image twice to get ActualWidth of Image control.

I've searched over SO (Why are ActualWidth and ActualHeight 0.0 in this case?) for solutions, but I wasn't able to get this working.

Below is my full code:

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>

code behind:

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();
            }
        }
    }
}

CroppingAdorner:

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
    }

}

and PunctedRect (I can't include code here because it exceeds question length limit, sorry for adding link)

What I'm trying to create is cropping tool that will work on Win7 and will allow me to select portion of image with aspect ratio.

As I wrote before I've tried fixing that ActualWidth problem, but I wasn't able to. How this can be fixed?

Can anyone suggest alternative (free) control that will have described functionality? There are many WUP (Windows Universal Platform) apps and controls, but I need Win7 compatible.

Community
  • 1
  • 1
Misiu
  • 4,738
  • 21
  • 94
  • 198

1 Answers1

2

Try calling UpdateLayout before accessing the ActualWidth for the first time.

this.UpdateLayout();

From 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.

Community
  • 1
  • 1
Dax Pandhi
  • 843
  • 4
  • 13