How to limit item position into Canvas? - c#

[EDIT]
Alright,
I edited this post since the code I posted back then had no real links with what I'm trying to do now, but the question is the same.
When I'm talking about limiting objects to a Canvas it was more like a Mouse Clipping, but as I read on many threads, this feature doesn't exist in SL. So I searched a bit around all forums and got this link. But I wasn't able to reproduce it all. Here is the code that is used for the Drag&Drops Events:
public class RoomImage : ContentControl
{
public RoomImage()
{
DefaultStyleKey = typeof(RoomImage);
}
public static readonly DependencyProperty BackgroundImageProperty = DependencyProperty.Register("Source", typeof(ImageSource), typeof(RoomImage), null);
public ImageSource BackgroundImage
{
get { return (ImageSource)GetValue(BackgroundImageProperty); }
set { SetValue(BackgroundImageProperty, value); }
}
//Instance Drag variable
private FrameworkElement _translateZone;
bool _isDrag;
Point StartingDragPoint;
public double Top
{
get { return (double)GetValue(Canvas.TopProperty); }
set { SetValue(Canvas.TopProperty, value); }
}
public double Left
{
get { return (double)GetValue(Canvas.LeftProperty); }
set { SetValue(Canvas.LeftProperty, value); }
}
//Instance Drag events
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_translateZone = GetTemplateChild("PART_TranslateZone") as FrameworkElement;
DefineDragEvents();
}
private void DefineDragEvents()
{
if (_translateZone != null)
{
_translateZone.MouseLeftButtonDown += new MouseButtonEventHandler(translateZone_MouseLeftButtonDown);
_translateZone.MouseLeftButtonUp += new MouseButtonEventHandler(translateZone_MouseLeftButtonUp);
_translateZone.MouseMove += new MouseEventHandler(translateZone_MouseMove);
}
}
private void translateZone_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_isDrag = true;
//start the drag
FrameworkElement DragBar = (FrameworkElement)sender;
DragBar.CaptureMouse();
// drag starting point
StartingDragPoint = e.GetPosition(this);
}
private void translateZone_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
FrameworkElement translateZone = (FrameworkElement)sender;
translateZone.ReleaseMouseCapture();
_isDrag = false;
}
private void translateZone_MouseMove(object sender, MouseEventArgs e)
{
if (_isDrag)
{
UIElement ui = (UIElement)this.Parent;
Point Point = e.GetPosition(ui);
Move(Point.X - StartingDragPoint.X, Point.Y - StartingDragPoint.Y);
}
}
public void Move(double left, double top)
{
Left = left;
Top = top;
}
}
I found this part of code in a tutorial where they didn't explain the Mouse.Clip at all. I can understand it and reuse it, but I have no clue where I could set the limits. The Parent of this item is a Canvas by the way.
If anyone can provide me some sort of code, or where I should implement mine it would be great!
Thank you, Ephismen.

A Canvas has no size as far as its children are concerned. It is just a relative starting point for rendering. A fixed canvas size is only relevant to the parent of the Canvas.
If you mean the objects are being drawn outside the canvas rectangle, then that is the correct behaviour for a canvas.
To stop objects being drawn outside a canvas you need to set a clipping rectangle in the Clip property of the Canvas.
Update:
Here is a very nice example here of how to have a ClipToBounds attached property. That is definitely the easiest way to implement bounds clipping I have seen.
Another update:
So you want to just keep the child items within the parent canvas. If your items vary in size & shape that is basically collision testing with the sides and cap the min/max left/top values. How complex are the shapes you are dropping? Rectangles are obviously very easy to calculate (as are circles).

Related

Clear UserControl with Dependency Property in Canvas

I have a number of UserControl (a Grid with few Labels) being generated and added to Canvas in runtime. I have implemented drag-and-drop for each UserControl and node line (or connector line) between UserControls.
When I clear the UserControl with myCanvas.Children.Clear(), I received the following error in method Node_LayoutUpdated():
This is my UserControl:
public partial class Foo : UserControl
{
public static readonly DependencyProperty AnchorPointProperty =
DependencyProperty.Register(
"AnchorPoint", typeof(Point), typeof(Foo),
new FrameworkPropertyMetadata(new Point(0, 0),
FrameworkPropertyMetadataOptions.AffectsMeasure));
public Point AnchorPoint
{
get { return (Point)GetValue(AnchorPointProperty); }
set { SetValue(AnchorPointProperty, value); }
}
private Canvas mCanvas;
public Foo(Canvas canvas, bool isInput)
{
InitializeComponent();
mCanvas = canvas;
this.LayoutUpdated += Node_LayoutUpdated;
}
void Node_LayoutUpdated(object sender, EventArgs e)
{
Size size = RenderSize;
Point ofs = new Point(size.Width / 2, size.Height / 2);
AnchorPoint = TransformToVisual(this.mCanvas).Transform(ofs);
}
}
Am I supposed to remove the DependencyProperty before removing the UserControl, and how? Can someone please explain what causes this error message and why?
You problem is the last line of your code. The LayoutUpdated event is invoked right after you remove (Clear) the children of the Canvas. TransformToVisual doesn't work if the Control is already detached from the VisualTree. Subscribing to parent layout events is usually neither required nor a good idea. A quick workaround would be to detach the control before the Clear.
Add this code to your UserControl:
public void Detach()
{
this.LayoutUpdated -= Node_LayoutUpdated;
}
And this to your MainWindow:
foreach(WhateverYourControlTypeIs control in myCanvas.Children)
{
control.Detach();
}
myCanvas.Children.Clear();

wpf rendering synchronization

I have two custom WPF controls (with custom rendering logic) placed on top of each other (transparent background and stuff). At some point i want to re-draw those controls. My first idea was to call:
foreach(var control in Controls)
{
control.InvalidateVisual();
}
But that did not quite work. I mean it does force the rendering of my controls, but visual update does not happen simultaneously. The controls update one by one, which does not look nice and can lead to confusion (in cases where one control displays an updated visual, while the other still displays an old one). The documentation on InvalidateVisual method is really poor, but i guess it is an async method, which is why i am having this issue.
So the question is: is there a way to synchronize rendering of multiple controls? Apart from creating one mega control containing all the rendering logic (i would like to avoid that).
You generally shouldn't use InvalidateVisual().
To update a control which is staying the same size, you can create a DrawingGroup and put that into the visual-tree during OnRender() -- then you can update the drawing group anytime you like using DrawingGroup.Open(), and WPF will update the UI.
For your case, this would look something like:
class Control1 : UIElement {
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}
private void Render(DrawingContext) {
// put your existing drawing commands here.
}
}
class Control2 : UIElement {
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}
private void Render(DrawingContext) {
// put your existing drawing commands here.
}
}
class SomeOtherClass {
public void SomeOtherMethod() {
control1.Render();
control2.Render();
}
}
You need to suspend refreshing control until you finish invalidating all of them one way is like this :
class DrawingControl
{
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
public static void SuspendDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
public static void ResumeDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
parent.Refresh();
}
}
see here for other ways : How do I suspend painting for a control and its children?
Another approach is to use data-binding to trigger the rendering if control has some Data property that is bound to viewmodel.
public static readonly DependencyProperty DataProperty
= DependencyProperty.Register("Data", typeof(DataType), typeof(MyView),
new FrameworkPropertyMetadata(default(DataType),
FrameworkPropertyMetadataOptions.AffectsRender));
public DataType Data
{
get { return (DataType)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
Note the AffectsRender flag. Then you can re-draw all controls simultaneously by simultaneously updating bound properties:
foreach(var viewModel in ViewModels)
{
viewModel.Data = ...;
}

How can I transfer the mouse position on the Canvas to the Ruler?

I have WPF application with Canvas and a Ruler. Now I need to pass Canvas OnMouseMove(MouseEventArgs e) to the Ruler to reflect mouse movements on its scale. Both controls are created independently while initialization, they do not know of each other. How can I transfer the mouse position on the Canvas to the Ruler?
MouseEventArgs has a GetPosition methos that allows you to pass any IInputElement as parameter, and returns the position of the mouse pointer relative to the specified element. Pass your Ruler as parameter value.
I found the solution, but I still have doubts. It seems to me a bit complicated:
Canvas as a "parent" control contains this piece of code:
public Point MousePosition
{
get { return (Point)GetValue(MousePositionProperty); }
set { SetValue(MousePositionProperty, value); }
}
public static readonly DependencyProperty MousePositionProperty =
DependencyProperty.Register("MousePosition", typeof(Point), typeof(DesignerCanvas), new UIPropertyMetadata(point));
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
MousePosition = e.GetPosition(this);
}
The Ruler contains the following code:
public Point Position
{
get { return (Point)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register("Position", typeof(Point), typeof(HorizontalRuler), new UIPropertyMetadata(defaultMousePoint,
new PropertyChangedCallback(PositionPropertyChangedCallback)));
private static void PositionPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
HorizontalRuler horizontalRuler = (HorizontalRuler)sender;
horizontalRuler.InvalidateVisual();
}

Radiobutton Class - Need to groub Radiobuttons into group

I have created my own radio button class – namely MyRadioButton, as the built in .NET class did not enlarge effectively. (using this for touch screen)
MyRadioButton Class works well, expect for an issue which I do not know How to resolve - When I have multiple MyRdaioButtons on a form, I can select all of them.... They somehow do not work as they should where when one selects one the others are automatically be deselected.
My code is as follows:
public class MyRadioButton : Control
{
public MyRadioButton()
{
}
private string textTowrite;
private bool checkStatus;
private int width;
private int height;
public event EventHandler CheckedChanged;
public delegate void MyHandler1(object sender, EventArgs e);
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
if (Checked)
Checked = false;
else
Checked = true;
Invalidate(true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
ButtonState btnstate;
Rectangle rRadioButton;
if (checkStatus)
{
btnstate = ButtonState.Checked;
}
else
btnstate = ButtonState.Normal;
rRadioButton = new Rectangle(0, 0, RBWidth, RBHeight);
FontFamily ft = new FontFamily("Tahoma");
Font fnt_radio = new Font(ft, (int)(18), FontStyle.Bold);
ControlPaint.DrawRadioButton(e.Graphics, -2, 10, rRadioButton.Width,
rRadioButton.Height, btnstate);
//RadioButton's text left justified & centered vertically
e.Graphics.DrawString(textTowrite, fnt_radio, new SolidBrush(Color.Black), rRadioButton.Right + 1, 16);
}
protected virtual void OnCheckedChanged(EventArgs e)
{
if (CheckedChanged != null)
{
CheckedChanged(this, e);
}
}
public override string Text
{
get { return textTowrite; }
set { textTowrite = value; }
}
public bool Checked
{
get { return checkStatus; }
set
{
checkStatus = value;
OnCheckedChanged(EventArgs.Empty);
}
}
public int RBWidth
{
get
{
if (width == 0)
{
width = 40;
}
return width;
}
set
{
if (width != value)
{
width = value;
Invalidate();
}
}
}
public int RBHeight
{
get
{
if (height == 0)
{
height = 40;
}
return height;
}
set
{
if (height != value)
{
height = value;
Invalidate();
}
}
}
}
If someone could provide me with a solution it would be greatly appreciated, as I am pulling out my hair
Thanks
Jens
You may also consider inheriting your control directly from RadioButton, giving you access to the RadioButton.GroupName property, or you will need to implement this type of functionality yourself as kbrinley has posted.
Have you considered using images on a RadioButton control instead? According to ButtonBase's documentation (which RadioButton inherits from):
To have the derived button control
display an image, set the Image
property or the ImageList and
ImageIndex properties.
Note that I have no idea how you'd do selected/unselected states with images... I imagine the ImageList is related to this.
Since this is your control, you will have to provide the logic for this to act like a radio button.
First, I'd suggest placing all of your Radio buttons into a Container control.
Then, at the beginning OnClick method of your control, use the GetContainerControl method to retrieve the Container object and iterate over all of the Radio buttons in the container and set the Checked property of them to false.

How to create a custom MultiSelector / ItemsControl in WPF / C#

I'm trying to create a diagramming application in C# / WPF. What I going for is somewhat similar to Microsoft Visio although I'm not trying to clone it. I kind of wrote this question as I was coding and just put all the issues I had into it in case someone will find it useful. Maybe I've been thinking too hard but I feel like I could throw up on my keyboard and produce better code, so feel free to give any suggestions on every detail you catch (grammar excluded :-))
In short:
Why are all the items positioned at (0,0)?
Code:
public class Diagram : MultiSelector
{
public Diagram()
{
this.CanSelectMultipleItems = true;
// The canvas supports absolute positioning
FrameworkElementFactory panel = new FrameworkElementFactory(typeof(Canvas));
this.ItemsPanel = new ItemsPanelTemplate(panel);
// Tells the container where to position the items
this.ItemContainerStyle = new Style();
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.LeftProperty, new Binding("X")));
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.TopProperty, new Binding("Y")));
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
FrameworkElement contentitem = element as FrameworkElement;
Binding leftBinding = new Binding("X");
Binding topBinding = new Binding("Y");
contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
contentitem.SetBinding(Canvas.TopProperty, topBinding);
base.PrepareContainerForItemOverride(element, item);
}
public class DiagramItem : ContentControl
{
private Point _location;
public DiagramItem()
{
}
static DiagramItem()
{
}
public Point Location
{
get { return _location; }
set
{
_location = value;
}
}
public double X
{
get { return _location.X; }
set
{
_location.X = value;
}
}
public double Y
{
get { return _location.Y; }
set
{
_location.Y = value;
}
}
}
//...
Ok, so the idea is that the Diagram : ItemsControl places its item on a Canvas panel at the position defined in the Item DiagramItem.Location. IOW when I change the X property in a DiagramItem the Diagram moves the item on the x-axis.
Note: MultiSelector is derived from ItemsControl and Selector and is only used here because I need the displayed item to be selectable.
Please note that I'd prefer not to use xaml if possible.
In long:
A Diagram instance as seen by the user has these requirements:
Has multiple DiagramItems.
User can select multiple DiagramItems.
DiagramItems can be resized, rotated and dragged anywhere on the Diagram.
Possible to navigate between DiagramItems using the keyboard.
I basically have two and possibly three classes relevant to this question.
Diagram extends System.Windows.Controls.Primitives.MultiSelector : Selector : ItemsControl
DiagramItem extends ContentControl or some other Control
The Diagram.ItemsPanel aka the visual panel which displays the items should be a panel which supports absolute positioning, like the Canvas.
How should I implement a class derived from MultiSelector and what resources can you point at which are relevant to this question?
What does one have to consider when implementing a custom MultiSelector / ItemsControl?
Resources:
I've found very few resources relevant to my issue, but then again I'm not sure what I'm supposed to be looking for. I've read the source code for ListBox and ListBoxItem using Reflector but didn't find it very useful.
Other resources:
System.Windows.Controls.Primitives.MultiSelector
System.Windows.Controls.ItemsControl
ItemsControl.ItemsPanel
System.Windows.Controls.Canvas
Positioning items when Canvas is the ItemsPanel of a ItemsControl
Using Templates to customize WPF controls
Create an items control
OK, apparently this can easily be achieved by using bindings and the property framework.
public class Diagram : MultiSelector
{
public Diagram()
{
this.CanSelectMultipleItems = true;
// The canvas supports absolute positioning
FrameworkElementFactory panel = new FrameworkElementFactory(typeof(Canvas));
this.ItemsPanel = new ItemsPanelTemplate(panel);
// Tells the container where to position the items
this.ItemContainerStyle = new Style();
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.LeftProperty, new Binding("X")));
this.ItemContainerStyle.Setters.Add(new Setter(Canvas.TopProperty, new Binding("Y")));
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
FrameworkElement contentitem = element as FrameworkElement;
Binding leftBinding = new Binding("XProperty");
leftBinding.Source = contentitem;
Binding topBinding = new Binding("YProperty");
topBinding.Source = contentitem;
contentitem.SetBinding(Canvas.LeftProperty, leftBinding);
contentitem.SetBinding(Canvas.TopProperty, topBinding);
base.PrepareContainerForItemOverride(element, item);
}
public class DiagramItem : ContentControl
{
public static readonly DependencyProperty XProperty;
public static readonly DependencyProperty YProperty;
public static readonly RoutedEvent SelectedEvent;
public static readonly RoutedEvent UnselectedEvent;
public static readonly DependencyProperty IsSelectedProperty;
public DiagramItem()
{
}
static DiagramItem()
{
XProperty = DependencyProperty.Register("XProperty", typeof(Double), typeof(DiagramItem));
YProperty = DependencyProperty.Register("YProperty", typeof(Double), typeof(DiagramItem));
SelectedEvent = MultiSelector.SelectedEvent.AddOwner(typeof(DiagramItem));
UnselectedEvent = MultiSelector.SelectedEvent.AddOwner(typeof(DiagramItem));
IsSelectedProperty = MultiSelector.IsSelectedProperty.AddOwner(typeof(DiagramItem));
}
public Double X
{
get
{
return (Double)this.GetValue(XProperty);
}
set
{
this.SetValue(XProperty, value);
}
}
public Double Y
{
get
{
return (Double)this.GetValue(YProperty);
}
set
{
this.SetValue(YProperty, value);
}
}
public Point Location
{
get
{
return new Point(X, Y);
}
set
{
this.X = value.X;
this.Y = value.Y;
}
}
}
The magic is in the proper usage of Bindings, the key was to add the contentitem as Source. The next step is obviously to handle the selection of items, but that's another question on its own.
If it is any help, I wrote a code project article based on my graphing and diagramming custom control called NetworkView:
http://www.codeproject.com/Articles/182683/NetworkView-A-WPF-custom-control-for-visualizing-a

Categories

Resources