Ответ 1
Ok. Это мое мнение о том, что вы описали.
Он выглядит следующим образом:
- Поскольку я не применяю никаких
RenderTransforms
, я получаю желаемую функциональность Scrollbar/ScrollViewer. - MVVM, который является способом перехода в WPF. Пользовательский интерфейс и данные независимы, поэтому DataItems имеют свойства
double
иint
для X, Y, Width, Height и т.д., Которые вы можете использовать в любых целях или даже хранить в базе данных. - Я добавил весь материал внутри
Thumb
для обработки панорамирования. Вам все равно нужно что-то сделать для Panning, которое возникает, когда вы перетаскиваете/изменяете размер ROI через ResizerControl. Я думаю, вы можете проверитьMouse.DirectlyOver
или что-то в этом роде. - Я фактически использовал
ListBox
для обработки ROI, чтобы вы могли иметь 1 выбранный ROI в любой момент времени. Это переключает функциональность изменения размера. Таким образом, если вы нажмете на ROI, вы увидите видимый видоискатель. - Масштабирование обрабатывается на уровне ViewModel, тем самым устраняя необходимость в пользовательских
Panels
или подобных вещах (хотя решение @Clemens также хорошо) - Я использую
Enum
и некоторыеDataTriggers
для определения фигур. См. РазделDataTemplate DataType={x:Type local:ROI}
. -
Скалы WPF. Просто скопируйте и вставьте мой код в
File -> New Project -> WPF Application
и посмотрите результаты для себя.<Window x:Class="MiscSamples.PanZoomStackOverflow_MVVM" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MiscSamples" Title="PanZoomStackOverflow_MVVM" Height="300" Width="300"> <Window.Resources> <DataTemplate DataType="{x:Type local:ROI}"> <Grid Background="#01FFFFFF"> <Path x:Name="Path" StrokeThickness="2" Stroke="Black" Stretch="Fill"/> <local:ResizerControl Visibility="Collapsed" Background="#30FFFFFF" X="{Binding X}" Y="{Binding Y}" ItemWidth="{Binding Width}" ItemHeight="{Binding Height}" x:Name="Resizer"/> </Grid> <DataTemplate.Triggers> <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="True"> <Setter TargetName="Resizer" Property="Visibility" Value="Visible"/> </DataTrigger> <DataTrigger Binding="{Binding Shape}" Value="{x:Static local:Shapes.Square}"> <Setter TargetName="Path" Property="Data"> <Setter.Value> <RectangleGeometry Rect="0,0,10,10"/> </Setter.Value> </Setter> </DataTrigger> <DataTrigger Binding="{Binding Shape}" Value="{x:Static local:Shapes.Round}"> <Setter TargetName="Path" Property="Data"> <Setter.Value> <EllipseGeometry RadiusX="10" RadiusY="10"/> </Setter.Value> </Setter> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> <Style TargetType="ListBox" x:Key="ROIListBoxStyle"> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <Canvas/> </ItemsPanelTemplate> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <ItemsPresenter/> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="ListBoxItem" x:Key="ROIItemStyle"> <Setter Property="Canvas.Left" Value="{Binding ActualX}"/> <Setter Property="Canvas.Top" Value="{Binding ActualY}"/> <Setter Property="Height" Value="{Binding ActualHeight}"/> <Setter Property="Width" Value="{Binding ActualWidth}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListBoxItem"> <ContentPresenter ContentSource="Content"/> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <DockPanel> <Slider VerticalAlignment="Center" Maximum="2" Minimum="0" Value="{Binding ScaleFactor}" SmallChange=".1" DockPanel.Dock="Bottom"/> <ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible" x:Name="scr" ScrollChanged="ScrollChanged"> <Thumb DragDelta="Thumb_DragDelta"> <Thumb.Template> <ControlTemplate> <Grid> <Image Source="/Images/Homer.jpg" Stretch="None" x:Name="Img" VerticalAlignment="Top" HorizontalAlignment="Left"> <Image.LayoutTransform> <TransformGroup> <ScaleTransform ScaleX="{Binding ScaleFactor}" ScaleY="{Binding ScaleFactor}"/> </TransformGroup> </Image.LayoutTransform> </Image> <ListBox ItemsSource="{Binding ROIs}" Width="{Binding ActualWidth, ElementName=Img}" Height="{Binding ActualHeight,ElementName=Img}" VerticalAlignment="Top" HorizontalAlignment="Left" Style="{StaticResource ROIListBoxStyle}" ItemContainerStyle="{StaticResource ROIItemStyle}"/> </Grid> </ControlTemplate> </Thumb.Template> </Thumb> </ScrollViewer> </DockPanel>
Код за:
public partial class PanZoomStackOverflow_MVVM : Window
{
public PanZoomViewModel ViewModel { get; set; }
public PanZoomStackOverflow_MVVM()
{
InitializeComponent();
DataContext = ViewModel = new PanZoomViewModel();
ViewModel.ROIs.Add(new ROI() {ScaleFactor = ViewModel.ScaleFactor, X = 150, Y = 150, Height = 200, Width = 200, Shape = Shapes.Square});
ViewModel.ROIs.Add(new ROI() { ScaleFactor = ViewModel.ScaleFactor, X = 50, Y = 230, Height = 102, Width = 300, Shape = Shapes.Round });
}
private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
//TODO: Detect whether a ROI is being resized / dragged and prevent Panning if so.
IsPanning = true;
ViewModel.OffsetX = (ViewModel.OffsetX + (((e.HorizontalChange/10) * -1) * ViewModel.ScaleFactor));
ViewModel.OffsetY = (ViewModel.OffsetY + (((e.VerticalChange/10) * -1) * ViewModel.ScaleFactor));
scr.ScrollToVerticalOffset(ViewModel.OffsetY);
scr.ScrollToHorizontalOffset(ViewModel.OffsetX);
IsPanning = false;
}
private bool IsPanning { get; set; }
private void ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (!IsPanning)
{
ViewModel.OffsetX = e.HorizontalOffset;
ViewModel.OffsetY = e.VerticalOffset;
}
}
}
Main ViewModel:
public class PanZoomViewModel:PropertyChangedBase
{
private double _offsetX;
public double OffsetX
{
get { return _offsetX; }
set
{
_offsetX = value;
OnPropertyChanged("OffsetX");
}
}
private double _offsetY;
public double OffsetY
{
get { return _offsetY; }
set
{
_offsetY = value;
OnPropertyChanged("OffsetY");
}
}
private double _scaleFactor = 1;
public double ScaleFactor
{
get { return _scaleFactor; }
set
{
_scaleFactor = value;
OnPropertyChanged("ScaleFactor");
ROIs.ToList().ForEach(x => x.ScaleFactor = value);
}
}
private ObservableCollection<ROI> _rois;
public ObservableCollection<ROI> ROIs
{
get { return _rois ?? (_rois = new ObservableCollection<ROI>()); }
}
}
ROI ViewModel:
public class ROI:PropertyChangedBase
{
private Shapes _shape;
public Shapes Shape
{
get { return _shape; }
set
{
_shape = value;
OnPropertyChanged("Shape");
}
}
private double _scaleFactor;
public double ScaleFactor
{
get { return _scaleFactor; }
set
{
_scaleFactor = value;
OnPropertyChanged("ScaleFactor");
OnPropertyChanged("ActualX");
OnPropertyChanged("ActualY");
OnPropertyChanged("ActualHeight");
OnPropertyChanged("ActualWidth");
}
}
private double _x;
public double X
{
get { return _x; }
set
{
_x = value;
OnPropertyChanged("X");
OnPropertyChanged("ActualX");
}
}
private double _y;
public double Y
{
get { return _y; }
set
{
_y = value;
OnPropertyChanged("Y");
OnPropertyChanged("ActualY");
}
}
private double _height;
public double Height
{
get { return _height; }
set
{
_height = value;
OnPropertyChanged("Height");
OnPropertyChanged("ActualHeight");
}
}
private double _width;
public double Width
{
get { return _width; }
set
{
_width = value;
OnPropertyChanged("Width");
OnPropertyChanged("ActualWidth");
}
}
public double ActualX { get { return X*ScaleFactor; }}
public double ActualY { get { return Y*ScaleFactor; }}
public double ActualWidth { get { return Width*ScaleFactor; }}
public double ActualHeight { get { return Height * ScaleFactor; } }
}
Shapes Enum:
public enum Shapes
{
Round = 1,
Square = 2,
AnyOther
}
PropertyChangedBase (класс MVVM Helper):
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
Управление разрешением:
<UserControl x:Class="MiscSamples.ResizerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Thumb DragDelta="Center_DragDelta" Height="10" Width="10"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Thumb DragDelta="UpperLeft_DragDelta" Height="10" Width="10"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Thumb DragDelta="UpperRight_DragDelta" Height="10" Width="10"
VerticalAlignment="Top" HorizontalAlignment="Right"/>
<Thumb DragDelta="LowerLeft_DragDelta" Height="10" Width="10"
VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<Thumb DragDelta="LowerRight_DragDelta" Height="10" Width="10"
VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
</Grid>
</UserControl>
Код за:
public partial class ResizerControl : UserControl
{
public static readonly DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double), typeof(ResizerControl), new FrameworkPropertyMetadata(0d,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double), typeof(ResizerControl), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(ResizerControl), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(ResizerControl), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public double X
{
get { return (double) GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public double Y
{
get { return (double)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
public double ItemHeight
{
get { return (double) GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
public double ItemWidth
{
get { return (double) GetValue(ItemWidthProperty); }
set { SetValue(ItemWidthProperty, value); }
}
public ResizerControl()
{
InitializeComponent();
}
private void UpperLeft_DragDelta(object sender, DragDeltaEventArgs e)
{
X = X + e.HorizontalChange;
Y = Y + e.VerticalChange;
ItemHeight = ItemHeight + e.VerticalChange * -1;
ItemWidth = ItemWidth + e.HorizontalChange * -1;
}
private void UpperRight_DragDelta(object sender, DragDeltaEventArgs e)
{
Y = Y + e.VerticalChange;
ItemHeight = ItemHeight + e.VerticalChange * -1;
ItemWidth = ItemWidth + e.HorizontalChange;
}
private void LowerLeft_DragDelta(object sender, DragDeltaEventArgs e)
{
X = X + e.HorizontalChange;
ItemHeight = ItemHeight + e.VerticalChange;
ItemWidth = ItemWidth + e.HorizontalChange * -1;
}
private void LowerRight_DragDelta(object sender, DragDeltaEventArgs e)
{
ItemHeight = ItemHeight + e.VerticalChange;
ItemWidth = ItemWidth + e.HorizontalChange;
}
private void Center_DragDelta(object sender, DragDeltaEventArgs e)
{
X = X + e.HorizontalChange;
Y = Y + e.VerticalChange;
}
}