How to redraw custom controls that are embedded in other custom controls? - c#

I'm building an app that has to be capable of plot a data series in C#. For this task, I started to build every part of the chart application as it follows:
An outer container represented by a class derived from System.Windows.Forms.UserControl, which is a container for the chart axes, plot area and chart title. This class has also the responsability to redraw all its child controls(by invalidate them).
A class derived from System.Windows.Forms.Control to represent the chart axes. The chart is supposed to have multiple Y-Axes and an unique X-Axis.
A class derived from System.Windows.Forms.Control where the data series will be shown.
The last two classes doesn't have a design view as I wrote them without using the templates provided for VS2010. Regardless, I set multiple properties of them to calculate their place and size within the container class, obviously this process is performed before the controls are drawn.
Well, the issue raises when I tried to Invalidate them from the container class. The classes have override the OnPaint method to handle the Paint event, which I trigger from the container class. The code for the OnPaint method in the container class looks like this
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Axis1.Invalidate();
Axis2.Invalidate();
PlotArea.Invalidate();
}
And as I stated above, every class has its respective OnPaint method. So guys, what I'm doing wrong?

Related

User Control on Active Report

I know I can draw on Active Reports, but let's leave that as a last resort.
I have a user control where all I do is draw graphics in the OnPaint method (Since that is the only way I can get the graphics to show). I know Active Reports is static, but I would have liked it to paint once. Instead I just get the gray box. Is there a way to accomplish this, or am I stuck converting my stuff to draw directly in Active Reports? I tried suspending the OnPaint method after running the code that draws the graphics, but that didn't help.
ActiveReports does support hosting a .net component. No need to paint directly on AR surface. It is not an ideal scenario but it should work. We would need more details to diagnose why your user control is not working. which section are you placing it in? is it bound or static graphics? you should know that AR paints the controls at processing time, it binds the controls then paints them to its own format (RDF) then the viewer only understands that format (think of it like a sheet of paper). This means that you need to place your control on the report and have it render it, rather than trying to render it in the viewer.
Anyway, more details about your control and the report you're using it in would help. please contact us at activereports.support#grapecity.com and we'll do our best to help.
thanks
http://activereports.grapecity.com
After working with GrapeCity I was able to get it working after making these 3 changes (I'm sure it's just the last that matters):
Switched to inherit from Control instead of UserControl
Overrode the OnPaint method instead of just subscribing to the Paint event
used the PaintEventArg e's e.Graphics instead of this.CreateGraphics()
Then declare it all in the report like so:
private void SectionReport1_ReportStart(object sender, EventArgs e)
{
GrapeCity.ActiveReports.SectionReportModel.CustomControl cc;
MyControl myc = new MyControl();
cc = new GrapeCity.ActiveReports.SectionReportModel.CustomControl(myc.GetType());
cc.Location = new PointF(1f, 1f);
cc.Size = new SizeF(4f, 4f);
this.detail.Controls.Add(cc);
}

Custom canvas drawing code in metro

In traditional desktop app, I perform my custom canvas drawing as follow
class ChartingView : System.Windows.Controls.Canvas
{
protected override void OnRender(DrawingContext drawingContext)
{
// ... All the juicy drawing code right here.
}
}
However, how about in Metro? As in Windows.UI.Xaml.Controls.Canvas, I cannot find OnRender method for me to override.
You can use Children property to populate Canvas. You can put there primitives (shapes, lines, etc.) as well as "complex" controls. And as noted in the #Aaron Murgatroyd comment:
there is no way to just simply draw on a canvas frame by frame
To adjust them on the Canvas you should use Canvas.SetXYZ methods (see for example Canvas.SetLeft and Canvas.SetTop methods).

Visual editing of graphical objects in WPF

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.

Implementing MS Access style 'relationships' GUI

I have no idea what the correct name for this UI style is. In MS Access the 'relationships' tool shows the db tables as little movable boxes that can be linked with lines. It's the same with Visio and a few audio apps - boxes that are movable, containing lines of text that can be joined together in a meaningful way.
How could I create a similar thing in .NET using Visual Studio 2008 and C#? I've never created my own controls before.
Here's an image of the sort of thing I mean: Click for example
You'll need two main custom controls: the main view and the table control.
The table control is responsible for drawing itself with all of its columns and ensuring that the item can scroll if need be. It is also responsible for providing an x/y co-ordinate for a specified row header. This is so that the relationship lines can match up to the correct row.
The main view is responsible for accepting a list of table objects (stored in a custom table object), creating the same number of table controls and arranging them in a specified order. It is also responsible for drawing the lines between the table controls.
All in all, this is not trivial. You'll want to override the OnPaint() method of both these controls to do all this custom drawing. Do some research on the GDI+ graphics routines to find out what methods you can use to draw this. You'll probably be using these objects/methods most often:
Pen
SolidBrush
LinearGradientBrush
DrawRectangle()
FillRectangle()
DrawString()
DrawImage()
DrawLine()
DrawPath()
You'll also need to trap all kinds of mouse events to enable moving the controls around. This can be done by overriding methods such as OnMouseDown or OnMouseMove.
Good luck.
The diagram you are trying to draw is an ERD or Database design. What you might also be looking for is a Class Diagram.
What you are trying to do is pretty complex.
Here are some links that might help. These are all open source type UML tools that do diagraming.
http://imar.spaanjaars.com/501/automatically-generating-class-diagrams-from-a-type-using-reflection
http://www.codebydesign.com/
http://sourceforge.net/projects/use-case-maker/
http://projects.gnome.org/dia/
http://www.monouml.org/doku.php?id=documentation

Winforms: SuspendLayout/ResumeLayout is not enough?

I have a library of a few "custom controls". Essentially we have our own buttons, rounder corner panels, and a few groupboxes with some custom paint. Despite the "math" in the OnPaint methods, the controls are pretty standard. Most of the time, all we do is draw the rounded corners and add gradient to the background. We use GDI+ for all that.
These controls are ok (and very nice looking according to our customers), however and despite the DoubleBuffer, you can see some redrawing, especially when there are 20++ buttons (for example) on the same form. On form load you see the buttons drawing… which is annoying.
I'm pretty sure that our buttons are not the fastest thing on earth but my question is: if double buffer is "on", shouldn't all that redraw happen in background and the Windows subsystem should show the results "instantly" ?
On the other hand, if there's "complex" foreach loop that will create labels, add them to a panel (double buffered) and change their properties, if we suspendlayout of the panel before the loop and resume layout of the panel when the loop is over, shouldn't all these controls (labels and buttons) appear "almost instantly"? This doesn't happen like that, you can see the panel being filled.
Any idea why this is not happening? I know it's hard to evaluate without sample code but that's hard to replicate too. I could make a video with a camera, but trust me on this one, it's not fast :)
We've seen this problem too.
One way we've seen to "fix" it is to completely suspend drawing of the control until we're ready to go. To accomplish this, we send the WM_SETREDRAW message to the control:
// Note that WM_SetRedraw = 0XB
// Suspend drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
...
// Resume drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
One of the things you should look at is whether you have set BackColor=Transparent on any of the child controls of your panels. The BackColor=Transparent will significantly degrade rendering performance especially if parent panels are using gradients.
Windows Forms does not use real transparency, rather it is uses "fake" one. Each child control paint call generates paint call on parent so parent can paint its background over which the child control paints its content so it appears transparent.
So if you have 50 child controls that will generate additional 50 paint calls on parent control for background painting. And since gradients are generally slower you will see performance degradation.
Hope this helps.
I'll approach your problem from a performance angle.
foreach loop that will create labels,
add them to a panel (double buffered)
and change their properties
If that's the order things are done, there's room for improvement. First create all your labels, change their properties, and when they are all ready, add them to the panel: Panel.Controls.AddRange(Control[])
Most of the time, all we do is draw
the rounded corners and add gradient
to the background
Are you doing the same thing over and over again? How are your gradients generated? Writing an image can't be that slow. I once had to create a 1680x1050 gradient in-memory, and it was really fast, like, too fast for Stopwatch, so drawing a gradient can't be so hard.
My advice would be to try and cache some stuff. Open Paint, draw your corners and save to disk, or generate an image in-memory just once. Then load (and resize) as needed. Same for the gradient.
Even if different buttons have different colors, but the same motif, you can create a bitmap with Paint or whatever and at runtime load it and multiply the Color values by another Color.
EDIT:
if we suspendlayout of the panel before the
loop and resume layout of the panel when the loop is over
That's not what SuspendLayout and ResumeLayout are for. They suspend the layout logic, that is, the automatic positioning of the controls. Most relevant with FlowLayoutPanel and TableLayoutPanel.
As for doublebuffering, I'm not sure it applies to custom draw code (haven't tried). I guess you should implement your own.
Doublebuffering in a nutshell:
It's very simple, a couple lines of code. On the paint event, render to a bitmap instead of rendering to the Graphics object, and then draw that bitmap to the Graphics object.
In addition to the DoubleBuffered property, also try adding this to your control's constructor:
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);
And if that ends up not being enough (which I'm gonna go out on a limb and say it isn't), consider having a look at my answer to this question and suspend/resume the redraw of the panel or Form. This would let your layout operations complete, then do all of the drawing once that's done.
You may want to look at the answer to my question, How do I suspend painting for a control and its children? for a better Suspend/Resume.
It sounds like what you are looking for is a "composited" display, where the entire application is drawn all at once, almost like one big bitmap. This is what happens with WPF applications, except the "chrome" surrounding the application (things like the title bar, resize handles and scrollbars).
Note that normally, unless you've messed with some of the window styles, each Windows Form control is responsible for painting itself. That is, every control gets a crack at the WM_ PAINT, WM_ NCPAINT, WM_ERASEBKGND, etc painting related messages and handles these message independently. What this means for you is that double buffering only applies to the single control you are dealing with. To get somewhat close to a clean, composited effect, you need to concern yourself not just with your custom controls that you are drawing, but also the container controls on which they are placed. For example, if you have a Form that contains a GroupBox which in turn contains a number of custom drawn buttons, each of these controls should have there DoubleBuffered property set to True. Note that this property is protected, so this means you either end up inheriting for the various controls (just to set the double buffering property) or you use reflection to set the protected property. Also, not all Windows Form controls respect the DoubleBuffered property, as internally some of them are just wrappers around the native "common" controls.
There is a way to set a composited flag if you are targeting Windows XP (and presumably later). There is the WS_ EX_ COMPOSITED window style. I have used it before to mix results. It doesn't work well with WPF/WinForm hybrid applications and also does not play well with the DataGridView control. If you go this route, be sure you do lots of testing on different machines because I've seen strange results. In the end, I abandoned used of this approach.
Maybe first draw on a control-only 'visible' (private) buffer and then render it:
In your control
BufferedGraphicsContext gfxManager;
BufferedGraphics gfxBuffer;
Graphics gfx;
A function to install graphics
private void InstallGFX(bool forceInstall)
{
if (forceInstall || gfxManager == null)
{
gfxManager = BufferedGraphicsManager.Current;
gfxBuffer = gfxManager.Allocate(this.CreateGraphics(), new Rectangle(0, 0, Width, Height));
gfx = gfxBuffer.Graphics;
}
}
In its paint method
protected override void OnPaint(PaintEventArgs e)
{
InstallGFX(false);
// .. use GFX to draw
gfxBuffer.Render(e.Graphics);
}
In its resize method
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
InstallGFX(true); // To reallocate drawing space of new size
}
The code above has been somewhat tested.
I had the same problem with a tablelayoutpanel when switching usercontrols that I wanted displayed.
I completely got rid of the flicker by creating a class that inherited the table, then enabled doublebuffering.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace myNameSpace.Forms.UserControls
{
public class TableLayoutPanelNoFlicker : TableLayoutPanel
{
public TableLayoutPanelNoFlicker()
{
this.DoubleBuffered = true;
}
}
}
I've had a lot of similar issues in the past, and the way I resolved it was to use a third-party UI suite (that is, DevExpress) rather than the standard Microsoft controls.
I started out using the Microsoft standard controls, but I found that I was constantly debugging issues which were caused by their controls. The problem is made worse by the fact that Microsoft generally does not fix any of the issues which are identified and they do very little to provide suitable workarounds.
I switched to DevExpress, and I have nothing but good things to say. The product is solid, they provide great support and documentation and yes they actually listen to their customers. Any time I had a question or an issue, I got a friendly response within 24 hours. In a couple of cases, I did find a bug and in both instances, they implemented a fix for the next service release.
I have seen bad winforms flicker on forms where the controls referred to a missing font.
This is probably not common, but it's worth looking into if you've tried everything else.

Categories

Resources