Visual editing of graphical objects in WPF - c#

There is a graphics WPF editor that is designed to work with diagrams. Because diagram can consist of very large number of objects, it was chosen to use Drawings for output. That means we create list of some business objects
class BusinessObject
{
// bunch of other properties and methods
public GeometryDrawing MyDrawing {get;set;}
}
pass it to helper that creates DrawingVisual for each drawing :
public List<Visual> BuildVisuals(List<BusinessObject> objectsList)
{
// foreach item in objectsList takes item.MyDrawing and draws it with DrawingContext
}
and then inject received data into drawing host
public class VisualHost : FrameworkElement
{
// FrameworkElement overrides, skipped
public readonly VisualCollection _children;
public VisualHost(List<Visual> visualsList)
{
_children = new VisualCollection(this);
foreach(var visual in visualsList)
{
_children.Add(visual);
}
// mouse handlers, other logic (skipped)
}
}
Everything works fine and quickly (even VisualHitTesting with backward mapping to respective business object with two-way data changing on-the-fly), but now there is a need to allow visual editing of objects - moving them around workspace, change size, scale ratio, etc. WPF Thumbs and Adorners come into mind (http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx), but they are intended to work with UIElement/Controls, that our Visuals aren't. Does anyone see a way to solve this problem without much changes of initial logic? Some workarounds or similiar functionality maybe. Rewriting the above mentioned code is not an option, we can't have 1000+ Control objects in memory if we use them on diagram. Thanks in advance.

Problem was solved manually - resizing and rotating adorners were drawn as separate objects over existing ones and added/removed from canvas on set/lost focus.

Related

C# UWP Universal Windows App - Shape Resizing

I'm working on an application that has a graphic editor. Aside from some other graphic components the main feature are some shapes (rectangles, triangles, stars etc.). The thing I would like to do is to have the ability to resize them after a double click, so there should be a container shown with points that can be dragged to resize the shape. There are many great tutorials out in the internet but mainly for rectangles or for other shapes that are "packed" into rectangle viewboxes.
The issue is that I need to have my resize points exactly on the vertexes, so for example I can't have a triangle inside a rectangle viewbox, but I need exactly three points - one on every vertex. The same applies for the other shapes like stars and arrows which are much more complicated.
Here's what I mean:
So I think I have two ways. Either pass a dynamic list of points after a double click and display them as separate shapes or binding them with my shape inside my shape class, but I cannot figure out how to add multiple shapes to a viewbox so that they keep their abilities. Here is a simplified snippet of how my shape classes look like:
public class RectangleObject : ShapeObject
{
private Rectangle _rectangle;
private Viewbox _viewbox;
public RectangleObject(Color fillColor)
{
_rectangle = new Rectangle()
{
Fill = new SolidColorBrush(fillColor),
Stretch = Stretch.Fill
};
_viewbox.child = _rectangle;
}
public void SetDimensions(){}
//... and some other methods
}
A nice solution would be if the viewbox could contain my shape and the resize points but I will appreciate any advice. I know that a canvas would be a better solution but all other components are packed in viewboxes so it would be problematic because of inheritance and would need many changes. Thanks in advance!
Yes I know exactly what you mean. I used CorelDraw back in the day and then Adobe Illustrator and both had the concept of shape transformation mode (your image on the right) as well as vertex edit mode (your image on the left). UWP is similar to WPF and WPF has a concept of adorners which you can customise to do whatever you want including what I just described.
So when you enter one of these modes, rather than modify your existing shape to show selectors; instead you create adorners that are superimposed over the shape. That way one doesn't clobber the other and you may have custom behavior for each as you indicated.
See also
Adorners - MSDN, https://msdn.microsoft.com/en-us/library/ms743737(v=vs.110).aspx, retrieved 2017-1-12

Drawing an Interactive User Interface in WPF

Is there any way to make a user interface in WPF that replicates the following diagram?
The idea was to have a control with three possible directions. In this control users could select the direction they want to add a new point.
For saving the point informations I was planning on using a structure like this:
private Node root;
public class Node
{
public int Key;
public string Value;
public Node left, right, forward;
//....
}
Is there any way to make a user interface in WPF that replicates the following diagram?
Yes. This is definitely possible in WPF. Many third party components, such as WPF Diagrams, demonstrate that this can be done.
If you are going to implement this yourself, I'd recommend by starting with Shapes and Basic Drawing in WPF. This tutorial walks through the basics of drawing on a WPF Canvas.
Here is the series tutorial how to make a WPF diagram designer step by step.
Part 1
Part 2
Part 3
Part 4
These series have used Adorner and Thumb object in WPF to build up WPF diagram designer which almost similar your figure.

How to create and connect custom user buttons/controls with lines using windows forms

I am trying to create some custom buttons or user controls as shown in the proposed GUI. The functionality should be as follows:
The graphs or configurations are created graphically.
The controls can be dragged from a toolbar or inserted by right mouse click/dropdown
By dragging from one control to another, they should be connected by lines
A toggle should shift the view from controls with options to a simple view
GUI view - controls with options:
GUI view - minimized:
Which functionality in Windows forms can I use to create the connecting lines ?
If they are created by using functionality to draw lines, how can I make sure the controls snap to the line? ..
I am programming in C# with Visual Studio 2010 Express.
Ok. This is a slight modification of the example I created for A similar requirement
My intention is to show that winforms is no longer an option for anyone who needs a serious UI.
The original sample was created in 3 man hours.
You might be surprised to know that the container that holds all these items (both nodes and connectors) is actually a ListBox.
Things worth noting:
The "NodeXX" text is contained within a Thumb control, which enables clicking and dragging.
The connectors can also be selected and show a nice animation when they are.
The left panel allows edition of the currently selected object's values.
The functionality of the UI is completely decoupled from the data that comprises it. Therefore all this nodes and connectors are simple classes with simple int and double properties that can be loaded/saved from a DB or whatever other data source.
If you dislike the way click sequences are done do draw nodes and connectors, that can be perfectly adapted to your needs.
WPF rules.
Edit:
Second version, this time much more similar to your original screenshot:
I added the concept of SnapSpot into the equation. These are the little red semi-circles you see around the nodes, which are actually what the Connectors are tied to.
I also changed the Connector DataTemplate to use a QuadraticBezierSegment based on
Connector.Start.Location,
Connector.MidPoint, and
Connector.End.Location
This allows curved lines to be used as connectors, not just straight lines.
There's a little red-square-shaped Thumb that will appear when you select (click) on a Connector, (visible in the screenshot) that will allow you to move the MidPoint of the curve.
You can also manipulate that value by rolling the mouse wheel when hovering the TextBoxes under "Mid Point" in the left panel.
The "Collapse All" CheckBox allows to toggle between full and small boxes, as shown in the screenshot.
The SnapSpots have an OffsetX OffsetY between 0 and 1 that corresponds to their position relative to the parent Node. These are not limited to 4 and could actually be any number of them per Node.
The ComboBoxes and Buttons have no functionality, but it's just a matter of creating the relevant properties and Commands in the Node class and bind them to that.
Edit2:
Updated download link with a much nicer version.
Edit 10/16/2014: Since a lot of people seem to be interested in this, I uploaded the source to GitHub.
I'm guessing this is a graph type problem. The nodes are the rooms and the edges are the lines that connect the rooms. You can introduce another class (say Connection class) that describes how nodes are connected to edges. For example, your hall connects to a bedroom, not necessarily using a straight line. The Graphics.DrawBezier allows you to draw curved lines, but requires an array of points. This is where the the Connection class comes in. Some code may help...
class Room
{
public Room(String name, Point location);
public void Draw(Graphics g);
}
class Connection
{
public void Add(Point ptConnection);
public void Add(Point[] ptConnection);
// Draw will draw a straight line if only two points or will draw a bezier curve
public void Draw(Graphics g);
}
class House // basically a graph
{
public void Add(Room room);
public void AddRoomConnection(Room r1, Room r2, Connection connector);
// draw, draw each room, then draw connections.
public void Draw(Graphics g);
}
void Main()
{
House myHouse = new House();
Room hall = new Room("Hall", new Point(120,10);
Room bedroom1 = new Room("Bedroom1", Point(0, 80));
Connection cnHallBedroom = new Connection();
cn.Add(new Point()); // add two points to draw a line, 3 or more points to draw a curve.
myHouse.AddRoomConnection(hall, bedroom1, cnHallBedroom);
}
This is basic approach, maybe not the best but might serve as a starting point.

Why does it take longer to toggle graphics after a selection is made from a combo box?

I have a WPF app that draws a compass. There is a large ring with tick marks and labels. I have a checkbox that toggles the compass graphics on and off. When I first start up the app, the compass turns on and off instantly.
Meanwhile, I have a combo box that grabs some data from a local database and uses that to render some overlay graphics. After using this combo box, the compass graphics no longer toggle quickly. In fact, the UI completely freezes for about 4 seconds whenever I click the checkbox.
I attempted to profile my app using Window Performance Profiling Tool for WPF. When I activated the checkbox, not only did my app freeze, so did the profiler. The graphs "catched up" afterward, but this tells me something must be seriously wrong.
I've managed to nail down that the problem graphics are the tick marks (not the numeric labels). If I eliminate them, the freezing problem stops. If I cut them down from 360 to, say, 36, the app still freezes, but for less time. Again, no matter how many tick marks I have, they toggle instantly when the app first starts.
My question is, How do I figure out why the toggle for my compass graphics goes from instant to horribly slow? I've tried extensive profiling and debugging, and I just can't come up with any reason why setting the Visibility on some tick marks should ever cause the app to freeze.
Edit
Okay, I've stripped everything out of my app to just the bare essentials, zipped it up, and uploaded it to Sendspace. Here is the link (it's about 143K):
http://www.sendspace.com/file/n1u3yg
[Note: don't accidentally click the banner ad, the real download link is much smaller and lower on the page.]
Two requests:
Do you experience the problem on your machine? Try opening Compass.exe (in bin\Release) and clicking the check box rapidly. The compass tick marks should turn on and off with no delay. Then, select an item from the combo box and try rapidly clicking the check box again. On my machine, it's very laggy, and after I stop rapid-fire clicking, it takes a few seconds for the graphics to catch up.
If you do experience the lag, do you see anything in the code that could be causing this odd behavior? The combo box is not connected to anything, so why should selecting an item from it affect the future performance of other graphics on the window?
Although ANTS didn't indicate a particular performance 'hotspot', I think that your technique is slightly flawed as it seems that every tick has a ViewModel that is responsible for handling an individual tick, and you are individually binding those ticks to the view. You end up creating 720 view models for these ticks that fire the a similar event each time the entire compass is shown or hidden. You also create a new LineGeometry every time this field is accessed.
The recommended approach for WPF in a custom drawn situation like this is to use a DrawingVisual and embrace the retained mode aspect of WPF's rendering system. There are several googleable resources that talk about this technique, but the gist is to declare a compass class inherits from FrameworkElement, and some smaller classes that inherit from DrawingVisual and use that to render the compass. With this technique, you can still have a ViewModel drive the compass behavior, but you wouldn't have individual viewmodels for each part of the compass. I'd be inclined to decompose the compass into parts such as bezel, arrow, sight, etc... but your problem may require a different approach.
class Compass : FrameworkElement
{
private readonly List<ICompassPart> _children = new List<ICompassPart>();
public void AddVisualChild(ICompassPart currentObject)
{
_children.Add(currentObject);
AddVisualChild((Visual)currentObject);
}
override protected int VisualChildrenCount { get { return _children.Count; } }
override protected Visual GetVisualChild(int index)
{
if (index < 0 || index >= _children.Count) throw new ArgumentOutOfRangeException();
return _children[index] as Visual;
}
override protected void OnRender(DrawingContext dc)
{
//The control automatically renders its children based on their RenderContext.
//There's really nothing to do here.
dc.DrawRectangle(Background, null, new Rect(RenderSize));
}
}
class Bezel : DrawingVisual
{
private bool _visible;
public bool Visible {
{
get { return _visible; }
set
{
_visible = value;
Update();
}
}
private void Update()
{
var dc = this.RenderOpen().DrawingContext;
dc.DrawLine(/*blah*/);
dc.Close();
}
}

How do I programmatically position a canvas in Silverlight?

I'm using Silverlight 3/C#, and I'm trying to create some XAML objects programatically in response to a user clicking on a button.
However, I can't seem to position a Canvas object that I have created - when I call SetValue() on the new Canvas with the Canvas.LeftProperty value, the browser window clears to an empty screen.
I have a simple function that exhibits the problem (I started out with something more complex):
private Canvas MakeNewCanvas()
{
Canvas newCanvas = new Canvas();
newCanvas.Width = newCanvas.Height = 50;
newCanvas.SetValue(Canvas.LeftProperty, 10);
return newCanvas;
}
and a simple button click handler that calls this:
private void MyButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
myPage.Children.Add(MakeNewCanvas());
}
NB: myPage is a Canvas object defined in my app's MainPage XAML file.
I've traced this through in the VS debugger with the SL app executing in Firefox, and whenever it executes the SetValue() line, the browser goes white and starts trying to download data (according to the status bar). I tried calling SetValue() after I've put the Canvas in the XAML tree with something like:
myPage.Children.Add(newCanvas);
newCanvas.SetValue(Canvas.LeftProperty, 10);
but it makes no difference - as soon as the SetValue() call is hit, the browser goes white.
The Canvas class seems to have no direct method of setting Left/Top on itself, apart from the SetValue() function with dependency property enum values. There's also Canvas.SetLeft() and Canvas.SetTop() but I guess they're just shims onto SetValue(). Using them didn't help anyway.
If I don't call SetValue(), then my canvas appears (and child objects I've added to it) in the SL app as you might expect, in the very top left.
If I create and position a Canvas object in Expression Blend, then the XAML generated includes Canvas.Left and Canvas.Top property values on the Canvas itself, as I would expect, so it seems to me that what I'm trying in C# should work.
But I don't seem to be able to set the left/top values myself from C# without the SL in the browser going all weird.
Any ideas?
Edit: my approach is correct, but canvas coords need to be floating point, not integer - see accepted answer for details.
Trying your code, but with the debugger catching exceptions, I get:
DependencyProperty of type System.Double cannot be set on an object of type System.Int32.
which is a really stupid gotcha - SetValue only takes Object, so you're prone to a problem like this.
Try either:
newCanvas.SetValue(Canvas.LeftProperty, 10.0);
or
Canvas.SetLeft(newCanvas, 10);
and it will probably work.
The behaviour of attached properties can be a little confusing at first.
The properties Canvas.LeftProperty and Canvas.TopProperty are applied to child objects of a canvas. They are therefore only meaningful when the child object is placed on a Canvas. Its important to understand the in WPF/SL objects do not position themselves, its up to the containing panel to decide where to put them.
I suspect the myPage is not of the Canvas type, its probably a Grid, hence it would have no idea what to do with such properties even if it bothered to look for them (which it doesn't).
In order for you to specifically position you new Canvas you need to be adding it to a Canvas.

Categories

Resources