Animation inside an adorner (calling OnRender) - c#

I'm using an Adorner in .NET 3.5, and I'm able to draw by overriding OnRender, but I need the ability to redraw the adorner to change its appearance.
Essentially I'm looking for a way to clear the drawing context and call OnRender again. What's the best way to do this, or is there a better approach?
public class MyAdorner : Adorner
{
private Brush brush = Brushes.Red;
public DragArrowAdorner(UIElement adornedElement) : base(adornedElement)
{}
public void RedrawWithBrush(Brush newBrush)
{
brush = newBrush;
// redraw..?
}
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
// some drawing code...
drawingContext.DrawRectangle(
brush,
null,
new Rect(AdornedElement.DesiredSize));
}
}

The answer to your question is use InvalidateVisual to cause the OnRender to be called again
However, I would suggest instead of doing custom drawing on OnRender yourself to use the standard styling and visual tree templating to build the actual visual of the adorner. This also means you can run standard XAML animations inside it with storyboards.
If you want to go with this approach, in your adorner class you need to:
in the constructor either call base.AddVisualChild() or create your own visuals collection with the visuals you want to show in the adorner
override ArrangeOverride(Size size) in order to arrange the children properly;
override VisualChildrenCount to return the number of children in the adorner visual tree;
override GetCisualChild(int index) to return a particular child.
You can take a look at the ResizingAdorner MSDN sample for more info.

It's very important to understand that WPF is not like Windows.Forms. OnRender() should really be called AccumulateDrawingObjects(), because that's what it's doing. WPF accumulates a bunch of drawing objects, which it retains to be able to draw the UI whenever it needs to. The magic of efficiently updating the UI is that you can actually change objects in that visual tree after OnRender().
For example, you can make a DrawingGroup "backingStore", and put that into the DrawingContext during OnRender. Then anytime you want to change the visual, you can DrawingGroup.Open(), put new drawing commands into it, and WPF will efficiently re-render that portion of the UI.
It looks like this:
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}

Related

Unable to draw rectangle on WindowsFormsHost using Adorners in WPF

In my WPF application, I have added WindowsFormsHost in one grid, I want to draw a rectangle on the control inside WinFormsHost.
Application layout:
Code I'm trying:
Adorner Class
public class SimpleRectAdorner : Adorner
{
// Be sure to call the base class constructor.
public SimpleRectAdorner(UIElement adornedElement)
: base(adornedElement)
{
}
// A common way to implement an adorner's rendering behavior is to override the OnRender
// method, which is called by the layout system as part of a rendering pass.
protected override void OnRender(DrawingContext drawingContext)
{
Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
// Some arbitrary drawing implements.
SolidColorBrush renderBrush = new SolidColorBrush(Colors.Green);
renderBrush.Opacity = 0.2;
Pen renderPen = new Pen(new SolidColorBrush(Colors.Navy), 1.5);
// Draw a circle at each corner.
Rect rect = new Rect(new Point(adornedElementRect.TopLeft.X, adornedElementRect.TopLeft.Y + 50), new Size(150, 50));
drawingContext.DrawRectangle(renderBrush, renderPen, rect);
}
}
Code to add adorner
private void btnDraw_Click(object sender, RoutedEventArgs e)
{
AdornerLayer.GetAdornerLayer(viewerGrid.Children[0]).Add(new SimpleRectAdorner(viewerGrid.Children[0]));
}
Is there any possible way to draw a rectangle on Control which is inside WindowsFormsHost?
Thanks in advance.
As noted in the comment by Clemens, WindowsFormsHost is rendered separately from the rest of your Window, and by necessity it is rendered on top of the Window. At first glance, this seems to be a design limitation about which you can do nothing; however, that is not strictly true.
If a second layer is added over your first layer, simply add a third layer on top of the second. Another Window or a Popup can render over the top of your WindowsFormsHost, and while you will have to jump through some hoops to make it all seem like part of the same Window--ensuring everything moves, minimizes, and restores at the same time, etc--it is certainly possible to do so.
You can use transparency in your third layer to allow the content in the WindowsFormsHost to show and be accessed. For example, you can set AllowsTransparency to true on your WPF Popup. It will be a bit of extra work, but if you absolutely need this feature, you can do it.

C# custom control cursor position

I'm making a custom control slider so I can adjust the appearance myself.
However, I can't find a way to get the cursor position relative to the control.
It would propably be easy to code for each control after plopping the controls into my program. But I'd like to have the full functionality inside the custom control project and only having to worry about getting a value from the slider once it's inside my program.
So I need to get the cursor tracking done inside the custom control project.
I've tried using the event here:
private void CustomSlider_MouseDown(object sender, MouseEventArgs e)
{
}
But the only position I'm able to get is the 'global' screen location of the cursor, which will not help me unless I know the position of the control.
I hope my question is clear, thanks.
To obtain the cursor position within the control's event-handler or corresponding virtual method, use the MouseEventArgs.Location(MSDN) property:
class CustomControl : Control {
protected override void OnMouseDown(MouseEventArgs e) {
Point cursorPos = e.Location;
}
}
To obtain the cursor position outside the control, use the Control.MousePosition(MSDN) static property and the PointToClient()(MSDN) method:
CustomControl ctrl = ...
Point cursorPos = ctrl.PointToClient(Control.MousePosition);

Creating semi-transparent panels/controls. Is there a foolproof way?

I am trying to create a semi-transparent control derived from System.Windows.Forms.Panel. [Edit: Basically what I want to achieve is this]:
I have gone through numerous web articles as well as SO questions and have come up with this:
class SeeThroughPanel : Panel
{
public SeeThroughPanel()
{
}
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
cp.ExStyle |= 0x00000020;
return cp;
}
}
protected override void OnPaint(PaintEventArgs e)
{
//base.OnPaint(e);
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(50, 0, 0, 0)), this.ClientRectangle);
}
}
Which results this (Note that this code too does not make it really semi-transparent. The button is displayed in full color, unlike in above image):
However, as soon as I do something that causes repaint of any other controls within the bounds of the semi-transparent panel, it is overdrawn (In this case, I have scrolled the underlying control):
So my question is, how can I avoid this? There has to be some way. After all, web browsers handle this kind of rendering all the time.
I have done a few tests now.
You are mixiing several problems into one, so let's take them apart:
No WebBrowser is written in C# using Winforms. So this is no reason why this must be possible.
Your button almost certainly is not in the Form's Controls collection. You need to either script it to be or use a little UI trick (*) to place it over another Control without adding it to it.
When you scroll you will see whatever the scrolling control report as its surface.
Here are two screenshots:
This is after startup:
..and this is after I scrolled a little to the right:
I have use this code to make sure the Z-order is right:
button1.Parent = this;
panel1.BringToFront();
button1.BringToFront();
seeThroughPanel1.BringToFront();
Note how the the space of the button is spared; this shows how the old surface is being used.
To work around this you would have to get at the current Form surfacse (maybe by Control.DrawToBitmap) and then use the right part of that to paint your 'semi-transparent' panel. Of course it would have to hide before you capture the form's current surface.
Of all terrible ideas I have had, this seems to be one of the worst..
* The trick is to move it over the container with the keyboard, not the mouse; with this trick it just moves without chnging its parent container. Also useful for placing a Control on top of the tabs of a TabControl... But I was too lazy for that, so I coded it..
The transparency feature of the Windows Forms control leaves much to be desired and is a blatant fudge. The control is not really transparent, it just pretends to be by looking at the background of it's parent control and copying the appropriate portion of the image or background onto it's own surface during the OnPaintBackground method.
This means that a "transparent" control placed on top of another on the same parent will in fact obscure the other child controls. Figure 1 shows this effect in action.
The panel control to the right of the form obscures the PictureBox control and shows only the background of the parent.
In order to make a truly transparent control we need to do a couple of things. Firstly, it's necessary to change the behaviour of the window by giving it a WS_EX_TRANSPARENT style. This is accomplished by overriding the CreateParams property so that the correct window style is included when the control is instantiated. The listing below shows this property override.
protected override CreateParams CreateParams
{
get
{
CreateParams cp=base.CreateParams;
cp.ExStyle|=0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
The second thing we need to do is to invalidate the parent of the control, not the control itself, whenever we need to update the graphics. This ensures that whatever is behind the control gets painted before we need to do our own graphics output. To do this, a routine such as that shown in the following listing is needed.
protected void InvalidateEx()
{
if(Parent==null)
return;
Rectangle rc=new Rectangle(this.Location,this.Size);
Parent.Invalidate(rc,true);
}
Finally, we need to ensure that the background draw routine does not mess up the recently repainted parent-form content by stubbing out the OnPaintBackground method.
protected override void OnPaintBackground(PaintEventArgs pevent)
{
//do not allow the background to be painted
}
Now the control is ready for the rest of its modification. It is important to note at this point that transparent controls are not suitable for double buffering using the standard SetStyle method. The memory bitmap which is provided to your code has an opaque background and does not allow the carefully retained parent pixels to show through.
To complete this article, a simple control that does nothing but paint moving ellipses on its surface is shown in the following listing.
using System;
using System.Drawing;
using System.Windows.Forms;
internal class SeeThroughPanel : Panel
{
public SeeThroughPanel()
{
}
protected void TickHandler(object sender, EventArgs e)
{
this.InvalidateEx();
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
protected void InvalidateEx()
{
if (Parent == null)
{
return;
}
Rectangle rc = new Rectangle(this.Location, this.Size);
Parent.Invalidate(rc, true);
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
private Random r = new Random();
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(128, 0, 0, 0)), this.ClientRectangle);
}
}

How to make a UserControls BackColor transparent in C#?

I created a simple stick man in a Windows Form User-Control (consisting of a radio button and three labels and one progress bar).
I set the back-color of the new user-control to transparent so that when I drag it onto my form, it blends with other colors and drawings on the form.
I am not getting what I'm trying to achieve.
Here is the picture:
UserControl already supports this, its ControlStyles.SupportsTransparentBackColor style flag is already turned on. All you have to do is set the BackColor property to Color.Transparent.
Next thing you have to keep in mind in that this transparency is simulated, it is done by asking the Parent of the control to draw itself to produce the background. So what is important is that you get the Parent set correctly. That's a bit tricky to do if the parent is not a container control. Like a PictureBox. The designer will make the Form the parent so you will see the form's background, not the picture box. You'll need to fix that in code, edit the form constructor and make it look similar to this:
var pos = this.PointToScreen(userControl11.Location);
userControl11.Parent = pictureBox1;
userControl11.Location = pictureBox1.PointToClient(pos);
In constructor set style of control to support a transparent backcolor
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
and then set Background to transperent color
this.BackColor = Color.Transparent;
From MSDN
A more complex approach (and possibly working one) is described here - with overrding of CreateParams and OnPaint.
Why all those things?
UserControl class has property Region.
Set this to what ever shape you like and no other adjustments are needed.
public partial class TranspBackground : UserControl
{
public TranspBackground()
{
InitializeComponent();
}
GraphicsPath GrPath
{
get
{
GraphicsPath grPath = new GraphicsPath();
grPath.AddEllipse(this.ClientRectangle);
return grPath;
}
}
protected override void OnPaint(PaintEventArgs e)
{
// set the region property to the desired path like this
this.Region = new System.Drawing.Region(GrPath);
// other drawing goes here
e.Graphics.FillEllipse(new SolidBrush(ForeColor), ClientRectangle);
}
}
The result is as in the image below:
No low level code, no tweaking, simple and clean.
There is however one issue but in most cases it can go undetected, the edges are not smooth and anti-aliasing will not help either.
But the workaround is fairly easy. In fact much easier than all those complex background handling..

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).

Categories

Resources