Best way to model block diagrams in WPF - c#

I want to write a block diagram design tool (something similar to Simulink or Modelica). I have done something like that already in C++/Qt and C#/WinForms/GDI+, but now I want to go WPF.
Take a block, which can be a rectangle or a square that contains several other smaller shapes as "input/output/bidirectional ports" probably labelled or not, some text and probably a bitmap or vector image. It should provide context menus, basic mouse or drag events (for moving the block, pulling the connections between blocks, etc.) and should allow manual rearrangement of its graphical constituents, maybe in a different editor.
Now imagine a diagram with say 1000 of such blocks (I am exaggerating a bit to allow enough headroom in the future) and corresponding connections. Given that scale, I wonder if I should fall back on to the visual level of WPF and model the interaction part manually or if it is sufficient to use Drawings, Shapes or even Controls for it (like a block being a button).
I am getting a little nervous when I see the ~50 event types a Button supplies and multiply this with the number of blocks times the average number of ports per block. Many elements will just point to the same event handlers or context menus, so these handlers could also be redirected to a management class.
I read through the respective WPF chapters in the book "Pro C# 5.0" and that actually did not allay my fears.
So what level of WPF (visual, drawing, shape, control) is advisable when it comes to speed and memory performance under these requirements?
Sidenote: I am just starting with WPF, so I am a bit stunned about its versatility. It makes strategic decisions a bit difficult for me, which is why I am asking before comprehensively researching.

You could try to create a custom layout with virtualization or use an existing one VirtualizationCanvas.
You need a custom panel for placing your scheme items at correct places in your scheme.
Also, you should create a custom control, based on ItemsControl with custom ItemsControlItems for handling items creating and so on.
Then apply your layout as ItemPanleTemplate for ItemsControl or ListView, where ItemsSource would be bounded to your viewModel with scheme collection.
That's the easiest option, as for me.

So now I have made a small feasibility study on the topic. Add 1800 Buttons to a Canvas, add two mouse events to each of them individually and further add a MouseWheel event to the window to allow basic zooming of the "scene". Observations are on my 10-year old Intel Core2Quad Q6600 + NVidia GTX 460 based machine.
Application starts up fast. Exe is very small (as expected), 8k. Resizing the window (to see more or less of the docked canvas' contents) feels quite snappy. However near fullscreen (all Buttons visible) redraw becomes a little heavy (like ~5Hz refresh rate). App grabs itself 20M of memory instead of 10M with only 1 Button. Zooming is fast enough. Moreover, when zoomed in, the rendering gets noticeably faster (so clipping and such works well). This is good because being in the closely zoomed-in state is the most frequent case when working with larger diagrams. Response to button events is unimpaired in every respect.
Conclusion: using Buttons for diagramming seems doable. It is certainly suboptimal as a graphics app gauged against the capabilities of my machine, but it is still fast enough for the job. Maybe using Shapes instead of Buttons gets a little more out of it. But definitely no case for falling back on low-level graphics.
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="wpf1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="wpf1"
Width="513"
Height="385"
x:Name="window1"
MouseWheel="window1_MouseWheel">
<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="canvas1" Background="#FFFFE8E8"/>
</Window>
...and...
public partial class Window1 : Window
{
double totalScale = 1;
public Window1()
{
InitializeComponent();
for (int i=0; i<60; i++)
{
for (int j=0; j<30; j++)
{
Button newButton = new Button();
canvas1.Children.Add(newButton);
newButton.Width = 25;
newButton.Height = 25;
Canvas.SetTop(newButton, j*30);
Canvas.SetLeft(newButton, i*30);
newButton.Click += new System.Windows.RoutedEventHandler(button1_Click);
newButton.MouseMove += new System.Windows.Input.MouseEventHandler(button1_MouseMove);
}
}
}
Button lastButton;
void button1_Click(object sender, RoutedEventArgs e)
{
lastButton = sender as Button;
lastButton.Background = Brushes.Blue;
}
void button1_MouseMove(object sender, MouseEventArgs e)
{
Button butt = sender as Button;
if (lastButton != butt) butt.Background = Brushes.Yellow;
}
void window1_MouseWheel(object sender, MouseWheelEventArgs e)
{
double scaleFactor = Math.Pow(1.0005,e.Delta);
totalScale *= scaleFactor;
canvas1.LayoutTransform = new ScaleTransform(totalScale, totalScale);
}
}

Related

Determine which rectangle on picturebox is clicked

I have a c# WinForms project with a picture box that contains a document with text. I am gathering the OCR data for the document using the Google Cloud Vision API, which works great. Using the bounding rectangles returned from the Google API, I am drawing rectangles around each word using DrawRectangle, and in the process I am associating that rectangle with the underlying word. What do I need to do to be able to just click on any given rectangle and know exactly which rectangle it is without having to take the point clicked and loop through all the coordinates of all the rectangles until I find it.
Four options for ya OP
Just loop
Take the point clicked and loop through all the coordinates of all the rectangles until I find it
This is actually the simplest answer and possibly the best performing answer for a relatively small (<1000) rectangles. If your rectangles might overlap, be sure to store and loop through them in z-order from front to back.
Assisted lookup
If you have a crap ton of rectangles, you could create an additional data structure to assist with lookups. For example, you could define a 10x10 array where each element contains a list of the rectangles that overlap a portion of the screen. That way you can narrow the search. Of course there is additional overhead of maintaining the list, so it may not be worth it, depending on your usage characteristics.
Custom controls
As an alternative, you could change your approach completely and render each rectangle as its own custom control. As a custom control, it will have a click event handler just like any Win32 window. However there is considerable overhead in instantiating and managing all those controls, so this is not recommended for a large number of rectangles. Also, under the covers I'm pretty sure it'll end up using the same lookup algorithms described above, so it won't perform any better.
Bindable class
A final option is to create a class specifically for the rectangle and "bind" it to the PictureBox (register as a consumer for its events). Then every rectangle will handle the click event and raise its own event if the click was within its boundaries. Here is an example to get you started:
class ClickableRectangle
{
private Rectangle _box;
public event EventHandler Click;
public ClickableRectangle(Rectangle coordinates)
{
_box = coordinates;
}
public void BindToControl(Control control)
{
control.MouseUp += Control_MouseUp;
control.Paint += Control_Paint;
}
private void Control_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(Pens.Black, _box);
}
private void Control_MouseUp(object sender, MouseEventArgs e)
{
if (!_box.Contains(e.X, e.Y)) return;
if (Click != null) Click(this, e);
}
}
Then to display a new rectange in MyPictureBox, and to handle them with a method called MyClickHandler, just call
var r = new ClickableRectangle(myRectangle);
r.BindToControl(MyPictureBox);
r.Click += this.MyClickHandler;
Voila.
See also this related question.

RichTextBox - UI Resize causes huge CPU Load

I've recently been developing an RTF Editor which is only a simple UserControl that has a RichTextBox with a couple Events like PreviewTextInput and PreviewMouseUp.
I noticed something slightly annoying though.
The Performance of the RichTextBox is absolutely terrible whenever the UI is being resized and the RichTextBox has a lot of Text to cause its Wrapping Algorithm to fire.
This gives the Application a really sloppy feel, as if it is poorly optimized (even though it isn't).
At first I noticed this performance hit while selecting Text, so instead of using the SelectionChanged event, I decided to use the PreviewMouseUp event and then fetch the Selection.
Then after further testing I found out that the resize also caused huge loads.
And I'm talking about loads ranging between 5% -> 30% with a Quad-Core CPU running at 3.8GHz!
To further test it out, I decided to comment out my RichTextBox and only include a new RichTextBox with no defined Property
<RichTextBox/>
Inserting this into a Window, filling with Text, and then resizing the Window to cause the Wrapping Algorithm did the same again, up to 30% usage!
I tried to research about this matter, and most people ended up recommending setting the PageWidth to high values in order to prevent Wrapping:
richTextBox1.HorizontalScrollBarVisibility = ScrollBarVisibility.Visible;
richTextBox1.Document.PageWidth = 1000;
Which I do not want, since the previous Version of the Editor I wrote was made with WinForms and could do Wrapping effortlessly, and I also want it in the new WPF Version.
Did anyone else ever face this issue?
If yes, could you please point me into the right direction to remove this huge strain on the hardware?
I'm a bit sad because I love WPF, but I did find one or the other Object that is really unoptimized and/or not practical in comparison to the WinForms counterpart, the RichTextBox seems to be another one of those cases :(
Sorry for the huge amount of Text, but I really wanted to Document this neatly in case some other poor soul faces this issue and for you guys to see what I've tried so far.
One way to overcome this issue might be to switch to "no wrap" mode when window is being resized, but when user finished with resizing - switch back to normal mode. Then wrapping algorithm will be executed just once at the end and users should still have smooth feeling about your application. Sample code:
public partial class MainWindow : Window
{
public MainWindow() {
InitializeComponent();
this.SizeChanged += OnSizeChanged;
}
private Timer _timer;
private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
// user started resizing - set large page width
textBox.Document.PageWidth = 1000;
// if we already setup timer - stop it and start all over
if (_timer != null) {
_timer.Dispose();
_timer = null;
}
_timer = new Timer(_ => {
// this code will run 100ms after user _stopped_ resizing
Dispatcher.Invoke(() =>
{
// reset page width back to allow wrapping algorithm to execute
textBox.Document.PageWidth = double.NaN;
});
}, null, 100, Timeout.Infinite);
}
}

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();
}
}

Drawing things on a Canvas

How would I draw something on a Canvas in C# for Windows Phone?
Okay, let me be a little more clear.
Say the user taps his finger down at 386,43 on the canvas. (the canvas is 768 by 480)
I would like my application to be able to respond by placing a red dot at 386,43 on the canvas.
I have no prior experience with Canvas whatsoever.
If this is too complex to be answered in one question (which it probably is), please give me links to other websites with Canvas and Drawing articles.
There are various ways of doing this. Depending on the nature of the red dot, you could make it a UserControl. For a basic circle, you can simply handle your canvas' ManipulationStarted event.
private void myCanvas_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
Ellipse el = new Ellipse();
el.Width = 10;
el.Height = 10;
el.Fill = new SolidColorBrush(Colors.Red);
Canvas.SetLeft(el, e.ManipulationOrigin.X);
Canvas.SetTop(el, e.ManipulationOrigin.Y);
myCanvas.Children.Add(el);
}
I think you need to approach the problem differently. (I'm not including code on purpose, because of that).
Forms and controls in an Windows applications (including Phone) can be refreshed for several reasons, at any time. If you draw on a canvas in response to a touch action, you have an updated canvas until the next refresh. If a refresh occurs the canvas repaints itself, you end up with a blank canvas.
I have no idea what your end goal is, but you likely want to either keep track of what the user has done and store that state somewhere and show it in a canvas on the repaint of the canvas. This could be done with storing all the actions and "replaying" them on the canvas, or simply storing the view of the canvas as a bitmap and reload the canvas with that bitmap when refreshed. But, in the later case I think using a canvas isn't the right solution.

Suggested (simple) approach for drawing large numbers of visual elements in WPF?

I'm writing an interface that features a large (~50000px width) "canvas"-type area that is used to display a lot of data in a fairly novel way. This involves lots of lines, rectangles, and text. The user can scroll around to explore the entire canvas.
At the moment I'm just using a standard Canvas panel with various Shapes placed on it. This is nice and easy to do: construct a shape, assign some coordinates, and attach it to the Canvas. Unfortunately, it's pretty slow (to construct the children, not to do the actual rendering).
I've looked into some alternatives, it's a bit intimidating. I don't need anything fancy - just the ability to efficiently construct and place objects in a coordinate plane. If all I get are lines, colored rectangles, and text, I'll be happy.
Do I need Geometry instances inside of Geometry Groups inside of GeometryDrawings inside of some Panel container?
Note: I'd like to include text and graphics (i.e. colored rectangles) in the same space, if possible.
Shapes are fairly heavy-weight. You should probably look into using graphics paths. Those are much more efficient when the user doesn't need to interact with individual parts of the drawing - and sometimes even then.
Try not to creating shapes that you do not need, and recycle ones that you already have. Basically no user will see the whole screen, so do NOT have the shapes that are out of sight. Don't create new ones f you can avoid - basically keep shapes falling out in a "ready" list, so you can reuse them.
If you have a large number of Shape instances, you could perhaps construct them asynchronously (on a worker thread) and queue up the actual Add operations via the Dispatcher. The idea here is that the UI won't be complete right away, but the user can start interacting right away, while elements continue loading.
EDIT: The above is incorrect. WPF does require that Visual elements be created on the UI thread. You can still accomplish this sort of 'lazy' visual loading using a pattern like this:
private Random _random = new Random();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Thread testThread = new Thread(TestThread);
testThread.Start();
}
private void TestThread()
{
for (int i = 0; i < 1000; i++)
{
Dispatcher.BeginInvoke((Action)CreateShape);
}
}
private void CreateShape()
{
var shape = new Rectangle();
shape.Width = _random.Next(10, 50);
shape.Height = _random.Next(10, 50);
shape.Fill = new SolidColorBrush(Colors.Red);
Canvas.SetLeft(shape, _random.Next(0, 400));
Canvas.SetTop(shape, _random.Next(0, 200));
LayoutRoot.Children.Add(shape);
}
This basically queues up tasks to be run 'asynchronously' on the UI thread (i.e. whenever the message pump is being serviced), so you can maintain responsiveness while performing the 'long' UI update.

Categories

Resources