图像控制 ActualWidth 仅在第二次加载后才正确 - Image Cropper
Posted
技术标签:
【中文标题】图像控制 ActualWidth 仅在第二次加载后才正确 - Image Cropper【英文标题】:Image control ActualWidth correct only after second load - Image Cropper 【发布时间】:2015-10-08 22:40:10 【问题描述】:我正在尝试从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(我无法在此处包含代码,因为它超出了问题长度限制,抱歉添加链接)
我正在尝试创建的是适用于 Win7 的裁剪工具,它允许我选择具有纵横比的图像部分。
正如我在尝试修复 ActualWidth 问题之前所写的那样,但我无法做到。如何解决这个问题?
任何人都可以建议具有描述功能的替代(免费)控件吗?有很多 WUP(Windows 通用平台)应用程序和控件,但我需要与 Win7 兼容。
【问题讨论】:
【参考方案1】:在第一次访问 ActualWidth 之前尝试调用 UpdateLayout。
this.UpdateLayout();
来自MSDN:
当您调用此方法时,IsMeasureValid 为 false 或 IsArrangeValid 为 false 的元素将调用特定于元素的 MeasureCore 和 ArrangeCore 方法,这会强制更新布局,并且所有计算的尺寸都将得到验证。
[...] 仅当您绝对需要更新大小和位置时才应调用 UpdateLayout,并且只有在您确定对您控制且可能影响布局的属性所做的所有更改都已完成之后。
【讨论】:
所以在我设置 Source of Image control (Crop.Source= new BitmapImage(new Uri(source));
) 后我可以立即调用UpdateLayout();
?
完美运行 :) 谢谢!以上是关于图像控制 ActualWidth 仅在第二次加载后才正确 - Image Cropper的主要内容,如果未能解决你的问题,请参考以下文章
SQL Reporting Services报告仅在第二次单击时加载