|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
Last Update11 February, 2008: Rubberband selection IntroductionIn the first article I have shown you how to drag, resize and rotate elements on a canvas. Today we are going to add further features that are essential for a typical diagram designer:
Using the CodeBesides the main
WPF is really amazing. A few classes together with some XAML code, that is all you need for this demo. DesignerCanvasThe public class DesignerCanvas : Canvas
{
private List<designeritem> selectedItems;
public List<designeritem> SelectedItems
{
get { return selectedItems; }
set { selectedItems = value; }
}
...
}
In the demo of my previous article, you probably have noticed one special shortcoming: when you drag an item outside the Wrapping the protected override Size MeasureOverride(Size constraint)
{
Size size = new Size();
foreach (UIElement element in base.Children)
{
double left = Canvas.GetLeft(element);
double top = Canvas.GetTop(element);
left = double.IsNaN(left) ? 0 : left;
top = double.IsNaN(top) ? 0 : top;
//measure desired size for each child
element.Measure(constraint);
Size desiredSize = element.DesiredSize;
if (!double.IsNaN(desiredSize.Width) && !double.IsNaN(desiredSize.Height))
{
size.Width = Math.Max(size.Width, left + desiredSize.Width);
size.Height = Math.Max(size.Height, top + desiredSize.Height);
}
}
//for aesthetic reasons add extra points
size.Width += 10;
size.Height += 10;
return size;
}
And finally the protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
if (e.Source == this)
{
this.dragStartPoint = new Point?(e.GetPosition(this));
foreach (DesignerItem item in selectedItems)
item.IsSelected = false;
selectedItems.Clear();
e.Handled = true;
}
}
DesignerItemThe public class DesignerItem : ContentControl
{
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool),
typeof(DesignerItem),
new FrameworkPropertyMetadata(false));
...
}
Then we have to implement an event handler for the protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);
DesignerCanvas designer = VisualTreeHelper.GetParent(this) as DesignerCanvas;
if (designer != null)
if ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control))
!= ModifierKeys.None)
if (this.IsSelected)
{
this.IsSelected = false;
designer.SelectedItems.Remove(this);
}
else
{
this.IsSelected = true;
designer.SelectedItems.Add(this);
}
else if (!this.IsSelected)
{
foreach (DesignerItem item in designer.SelectedItems)
item.IsSelected = false;
designer.SelectedItems.Clear();
this.IsSelected = true;
designer.SelectedItems.Add(this);
}
e.Handled = false;
}
Please note that we handle the
Now we have to update the template for the <!-- DesignerItem Style -->
<Style TargetType="{x:Type s:DesignerItem}">
<Setter Property="MinWidth" Value="25"/>
<Setter Property="MinHeight" Value="25"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type s:DesignerItem}">
<Grid DataContext="{Binding RelativeSource=
{RelativeSource TemplatedParent}}">
<!-- PART_DragThumb -->
<c:DragThumb x:Name="PART_DragThumb" Cursor="SizeAll"/>
<!-- PART_ResizeDecorator -->
<Control x:Name="PART_ResizeDecorator"
Visibility="Collapsed"
Template="{StaticResource ResizeDecoratorTemplate}"/>
<!-- PART_ContentPresenter -->
<ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding ContentControl.Content}"
Margin="{TemplateBinding ContentControl.Padding}"/>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Value="True" Binding=
"{Binding RelativeSource={RelativeSource Self},Path=IsSelected}">
<Setter TargetName="PART_ResizeDecorator" Property="Visibility"
Value="Visible"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ToolboxThe public class Toolbox : ItemsControl
{
private Size itemSize = new Size(65, 65);
public Size ItemSize
{
get { return itemSize; }
set { itemSize = value; }
}
protected override DependencyObject GetContainerForItemOverride()
{
return new ToolboxItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is ToolboxItem);
}
}
Additionally we want the <Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Margin="0,5,0,5"
ItemHeight="{Binding Path=ItemSize.Height,
RelativeSource={RelativeSource AncestorType=s:Toolbox}}"
ItemWidth="{Binding Path=ItemSize.Width,
RelativeSource={RelativeSource AncestorType=s:Toolbox}}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
ToolboxItemThe public class ToolboxItem : ContentControl
{
private Point? dragStartPoint = null;
static ToolboxItem()
{
FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(ToolboxItem),
new FrameworkPropertyMetadata(typeof(ToolboxItem)));
}
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);
this.dragStartPoint = new Point?(e.GetPosition(this));
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton != MouseButtonState.Pressed)
{
this.dragStartPoint = null;
}
if (this.dragStartPoint.HasValue)
{
Point position = e.GetPosition(this);
if ((SystemParameters.MinimumHorizontalDragDistance <=
Math.Abs((double)(position.X - this.dragStartPoint.Value.X))) ||
(SystemParameters.MinimumVerticalDragDistance <=
Math.Abs((double)(position.Y - this.dragStartPoint.Value.Y))))
{
string xamlString = XamlWriter.Save(this.Content);
DataObject dataObject = new DataObject("DESIGNER_ITEM", xamlString);
if (dataObject != null)
{
DragDrop.DoDragDrop(this, dataObject, DragDropEffects.Copy);
}
}
e.Handled = true;
}
}
}
Rubberband SelectionRubberband selection is best handled with an public class RubberbandAdorner : Adorner
{
private Point? startPoint;
private Point? endPoint;
...
}
The protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton != MouseButtonState.Pressed)
this.dragStartPoint = null;
if (this.dragStartPoint.HasValue)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
if (adornerLayer != null)
{
RubberbandAdorner adorner = new RubberbandAdorner(this, dragStartPoint);
if (adorner != null)
{
adornerLayer.Add(adorner);
}
}
}
e.Handled = true;
}
An protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (!this.IsMouseCaptured)
this.CaptureMouse();
endPoint = e.GetPosition(this);
UpdateSelection();
this.InvalidateVisual();
}
else
{
if (this.IsMouseCaptured) this.ReleaseMouseCapture();
}
e.Handled = true;
}
While the mouse button is pressed, the private void UpdateSelection()
{
foreach (DesignerItem item in designerCanvas.SelectedItems)
item.IsSelected = false;
designerCanvas.SelectedItems.Clear();
Rect rubberband = new Rect(startPoint.Value, endPoint.Value);
foreach (Control child in designerCanvas.Children)
{
DesignerItem item = child as DesignerItem;
if (item != null)
{
// I LOVE WPF
Rect itemRect = VisualTreeHelper.GetDescendantBounds(item);
Rect itemBounds = item.TransformToAncestor
(designerCanvas).TransformBounds(itemRect);
if (rubberband.Contains(itemBounds))
{
item.IsSelected = true;
designerCanvas.SelectedItems.Add(item);
}
}
}
}
After cleaning the current selection I create the actual rubberband rectangle and then I check for each Customize the DragThumbThe default style of the <Path Stroke="Red" StrokeThickness="5" Stretch="Fill" IsHitTestVisible="false"
Data="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z"/>
To illustrate the result I have colorized the default
Now try the following: <Path Stroke="Red" StrokeThickness="5" Stretch="Fill" IsHitTestVisible="false"
Data="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z">
<s:DesignerItem.DragThumbTemplate>
<ControlTemplate>
<Path Data="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z"
Fill="Transparent" Stretch="Fill"/>
</ControlTemplate>
</s:DesignerItem.DragThumbTemplate>
</Path>
The result is a
OutlookIn the coming article, I will show you how to connect
CreditsMost of the control styles are taken from the WPF SDK samples. History
|
||||||||||||||||||||||