How can I use a meter-style progress bar? - winforms

In Vista/7, the Windows Explorer shell window makes use of a special kind of static progress bar to display hard drive space.
With default styles, this bar is blue colored and non-animated. It also turns red colored when it gets close to being full (low disk space).
Using messaging, I can tell the Windows Forms ProgressBar control to update its state to Paused and Error (yellow and red colored, respectively), which works fine, but these are still specific to progress.
In the Windows User Experience Guidelines, it specifically points out this "meter" variant of the Progress Bar:
This pattern isn't a progress bar, but
it is implemented using the progress
bar control. Meters have a distinct
look to differentiate them from true
progress bars.
They say it "is implemented using the progress bar control", so... how? What message could I send to the control to have it behave this way?
I've seen that you can send messages for setting the bar color, but the documentation says these calls are ignored when visual styles are enabled. Nothing else in the Windows API documentation for raw ProgressBar controls seemed to suggest a way to do this. Am I just stuck making a custom drawn bar? I'd really like to utilize the OS whenever possible so that the application will appear consistent throughout different OS versions. I realize that pre-Vista versions probably won't support this, though.
I'm looking for a Windows Forms solution, but I wonder if it is even exposed at all via Win32 API.

It is possible, but not through ProgressBar. Nor does Win7 use a PB to draw those meters, there is no window handle associated with the bar. It must be using custom drawing. That's possible in WinForms as well with the VisualStyleRenderer class. One thing that doesn't help however is that the required visual style parts and states are not declared, not even in .NET 4.0.
This sample form reproduces the meter bar:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
VisualStyleRenderer renderer = new VisualStyleRenderer(VisualStyleElement.ProgressBar.Bar.Normal);
protected override void OnPaint(PaintEventArgs e) {
renderer.SetParameters("PROGRESS", 11, 2);
renderer.DrawBackground(e.Graphics, new Rectangle(10, 10, 200, 15));
renderer.SetParameters("PROGRESS", 5, 4);
renderer.DrawBackground(e.Graphics, new Rectangle(10, 10, 100, 15));
}
}
}
I got the part and state numbers from the vsstyle.h SDK header file.

Related

Overlay WinForms controls on DirectX content

I'm trying to create a graphics library with graphics control. The control enables adding overlay controls. The graphics on the control uses DirectX and is part of the library, while the overlay controls are provided by end users of the library which are not graphics experts. Thus stability and convenient API are the most important issues.
For technical reasons I need to show the graphics by DirectX directly on the control and cannot host the DirectX scene using another framework as WPF (For more information see my previous question: Stereoscopic 3D on WPF).
I think the most problematic issues are:
Transparency (and semi-transparency...) within the area of the control itself.
Animations on the overlay control: That's why control rasterization (e.g. by WPF) is not an option.
My tries until now:
Following the lot of posts discussed similar issues I decided to use WS_EX_TRANSPARENT. I was surprised to see that although the Airspace issue, I can see the DirectX content under the transparent regions of the overlay control. However the overlay control was not shown except for when I resized the window - then it blinks and disappears again. Here is the code of the overlay control:
class GDIGraphicsControl : UserControl
{
private const int WS_EX_TRANSPARENT = 0x20;
protected override CreateParams CreateParams
{
get
{
CreateParams p = base.CreateParams;
p.ExStyle |= WS_EX_TRANSPARENT;
return p;
}
}
public GDIGraphicsControl()
{
this.Dock = DockStyle.Fill;
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// Do nothing
}
protected override void OnPaint(PaintEventArgs e)
{
for (int i = 0; i < 10; ++i)
{
int alpha = 25 * i;
int yPos = 10 * i;
e.Graphics.FillRectangle(
new SolidBrush(Color.FromArgb(alpha, Color.Green)),
5, 5 + yPos, 100, 10);
}
e.Graphics.FillEllipse(new SolidBrush(Color.Red), 110, 5, 100, 100);
}
}
To analyze the blinking problem I tried to make things simpler. First for debugging purpose I tried to use GDI rendering instead of DirectX. I implemented it such that it will be very similar to the DirectX rendering - especially the Form.SetStyle and the Invalidate() call in every OnPaint() which I thought to be the cause to the problem. The problem do exist also in GDI-only rendering. Here is the GDI-rendering parent control code:
public partial class RenderingForm : Form
{
public RenderingForm()
{
InitializeComponent();
ControlStyles styles =
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint |
ControlStyles.Opaque;
this.SetStyle(styles, true);
Button button = new Button()
{
Text = "Just a button",
Left = 5,
Top = 210,
Width = 200
};
this.Controls.Add(button);
this.Controls.Add(new GDIGraphicsControl());
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// Do nothing
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Color.Blue);
this.Invalidate();
base.OnPaint(e);
}
}
I tried to add a simple overlay Button to check if the problem exist when no transparency is used (see in the above code). The button doesn't blink, but instead of the button I see garbage on the screen until resizing the control first time - then the button appears correctly.
I tried to invalidate the child overlay controls but it has no effect. I tried to invalidate it both in parent's OnPaint and in the child OnPaint to create the message-pump rendering effect and it has no effect. Setting the control's style as in parent rendering control didn't resolve the problem and caused the background to blink in black.
I also performed some tries using WPF but the question is long enough for now without hard Airspace issue...
Now for the questions:
Can someone explain how does GDI overlay over DirectX graphics work? Especially I don't understand it because I know that WinForms transparency is working by that the children controls render on the parent's Device Context - and DirectX has a hardware rendering context. Does it mean that the DirectX texture is copied back to software?
Why does overriding the CreateParams causes the Control to blink and how to prevent it?
What are performance cost do those transparency methods (overriding CreateParams, BackColorTransparency = True, Control.SetStyle+override OnPainBackground)? It is of high importance that the underlying graphics will be rendered efficiently but I don't care about the overlay performance (except for lightweight animation).
I've done a LOT of research in this area and I've come to the conclusion that Airspace is the way to go. I've collected sources from numerous projects around the web and put them into a single project that allows you to integrate WPF with XNA. I've also done a version of this that was pure DirectX, but to be honest the XNA version is a lot more straightforward and better suited to C#. I don't have enough room to post all the details here but you can download the demo from my website and probably figure out where to go from there. Good luck!

How to make label transparent without any flickering at load time

I have a panel and on that I've a picturebox. There are around 20 labels that I've to show in the panel. I want the background of Label to be transparent ie the image in picturebox is shown and the label displays only the text.
Now since labels do not exhibit true transparency I made the labels child of picturebox
this.lbl1.Parent = pictureBox1;
This has solved my immediate problem but now when the form loads, all the labels take a while to become visible and do so one at a time. I'd appreciate if you guys can give some solution for this.
Thanks in advance
The standard cure for flicker is double-buffering. But that cannot solve this kind of flicker. It is a different kind, caused by having multiple windows overlapping each other. Each label is its own window. When the form needs to paint itself, it draws its background leaving holes for the child windows. Each child window then takes a turn drawing itself. And their child windows draw themselves next. Etcetera.
This becomes noticeable when one control takes a while to draw, no doubt your picture box. Especially when it displays a large image that needs to be resized. The holes for the child windows stay unpainted while the picture box draws. They have a white background, black when you use the form's TransparencyKey or Opacity property. This can contrast badly with the image in your picture box, that effect is perceived by the user as flicker.
One immediate cure is to not use controls so you don't pay for their window. A Label is very convenient but it is a massive waste of system resources to burn up a window just to display a string. You can simply implement the picture box' Paint event and draw the strings with TextRenderer.DrawText(). PictureBox has double-buffering turned on by default so the image as well as the text is drawn completely smoothly, no more flicker. The obvious disadvantage is that you lose the convenience of point-and-click, you have to write code.
There are other fixes possible. One of them is to prevent the picture box from leaving holes for the child windows. It will draw the entire image, the labels pop on top of them. That's still flicker but not nearly as noticeable. Add a new class to your project and paste this code:
using System;
using System.Windows.Forms;
internal class MyPictureBox : PictureBox {
protected override CreateParams CreateParams {
get {
var parms = base.CreateParams;
parms.Style &= ~0x02000000; // Turn off WS_CLIPCHILDREN
return parms;
}
}
}
Compile and drop the new picture box control from the top of the toolbox onto your form.
Yet another possible workaround is to make the form and all of its children double-buffered. This doesn't speed up the painting at all but all of the windows get rendered into a memory buffer, the result is blitted to the screen. You'll notice a delay but the window suddenly pops on the screen. This is called compositing. Winforms doesn't support this directly since it can have side-effects but it is easy to enable. Paste this code into your form class:
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
Supported by XP and later. Watch out for painting artifacts.
or you can ditch the labels and draw the text yourself:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
TextRenderer.DrawText(e.Graphics, "Label1", SystemFonts.DefaultFont,
new Point(10, 10), Color.Black, Color.Empty);
}
The label does not support transparency, you must create your own unique custom control, you can see these code examples.
http://www.codeproject.com/KB/dotnet/transparent_controls_net.aspx http://www.codeproject.com/KB/vb/uLabelX.aspx
Bye

WPF hosting a WindowsFormHost what are my drawing options?

I have a WPF app which hosts a WindowsFormHost. The WindowsFormHost loads ESRI's ArcEngine. The ArcEngine has some drawing functions available but I'm looking for more power and control. Assuming I want to draw some images & text on the screen over the image created by the ArcEngine, what are my options, if any?
A WPF window cant render anything over any winforms elements that it contains. You either need to do the drawing within the winforms element or do a nasty hack of creating a separate WPF window with a transparent background & no border that you programatically move around to keep on top of the winforms element - then you can draw into this overlay window using WPF mechanisms. it's ugly but effective.
You can implement your Custom Layer. Inside the layer you can implement your drawing method with GDI+ or OpenGL (in Dynamic Display mode).
Create class that implements ILayer interface. In this case you can to manage your drawing as you wish.
There is an exmaple of simple implementation with GDI+
[Guid("0c745c09-a67a-4736-ba8c-23238582f78f")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("CustomLayerPan.customLayer")]
public class customLayer : ESRI.ArcGIS.ADF.BaseClasses
{
public customLayer(List<IGeometry> p_objItems)
{
}
public override void Draw(ESRI.ArcGIS.esriSystem.esriDrawPhase drawPhase, ESRI.ArcGIS.Display.IDisplay Display, ESRI.ArcGIS.esriSystem.ITrackCancel trackCancel)
{
Graphics objGraphics = Graphics.FromHdc(new IntPtr(((IScreenDisplay)Display).hDC)) as Graphics;
//Draw here using GDI+
}
}

Creating a semi-transparent blurred background WPF

I have a border, i want the background of this border to be partially transparent (opacity 0.8) but i do not want the image behind it to be well defined. The effect i am after is similar to the Windows Vista window border effect, where you can see that something is behind it but you cant tell what it is.
A few clarifications:
I am working on Windows XP, so i cant use Vista Glass
I need this solution to be portable across any windows platform
Any help would be appreciated :)
Extend Glass Frame Into a WPF Application
WPF Window with aero glass background. (C# .NET)
DWM Blur Behind Overview
Hope one of those links helps. I used the 1'st link I had to add this class to make it work:
#region WINAPI Crap, none should handle this in 21st century
private class NonClientRegionAPI
{
[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
public int cxLeftWidth; // width of left border that retains its size
public int cxRightWidth; // width of right border that retains its size
public int cyTopHeight; // height of top border that retains its size
public int cyBottomHeight; // height of bottom border that retains its size
};
[DllImport("DwmApi.dll")]
public static extern int DwmExtendFrameIntoClientArea(
IntPtr hwnd,
ref MARGINS pMarInset);
}
#endregion
This is the best I could find on the net:
http://blogs.msdn.com/unnir/archive/2006/03/01/541154.aspx
Considering the above is by a Microsoft guy, you'd be hard pressed to find a better way to do it.
It does a simple transparency on the window, so not quite like Aero glass. Aero effect is hardware accelerated and most certainly uses Direct3D in some way.
This SO answer talks about it in some detail:
Is it possible to achieve the "Aero Glass" look on XP?
In Silverlight there is a Blur effect (http://www.silverlightshow.net/tips/Using-Blur-and-DropShadow-effects-in-Silverlight-3.aspx) should also be available for WPF (http://searchwindevelopment.techtarget.com/tip/0,289483,sid8_gci1377157,00.html) you can apply to controls. Maybe combining this with opacity would help you achive the desired goal?

Rotating a .NET panel in Windows Forms

We use Windows Forms and custom user controls, and I would like to be able to rotate the panel hosting the userControl in a particular form. I have seen similar functionnalities with WPF, but I can't use it for the moment. Is it possible to achieve the rotation of a panel and its children using possibly built-in .NET methods or GDI+?
I have seen some pretty cool visual effect with menus that are displayed in game development, so I was wondering if it would be possible to create similar effects using Windows Forms.
Rotating a panel and its children in Windows Forms is not something directly supported, and I think it will end up being a buggy headache that could easily suck up lots of time. It's especially painful to think about when you could do this in WPF with zero lines of C# code and only a tiny bit of XAML.
You can use rotations in GDI+ by calling the RotateTransform method on a Graphics object.
However, rotating an entire control is not so simple, and will depend heavily on how the control is implemented.
If it's a composite UserControl that has other controls inside of it, you're out of luck.
If it's a sinlge control that paints itself, try inheriting the control, overriding the OnPaint method, and calling RotateTransform on the Graphics object. However, you will probably have trouble with it. In particular, you will probably need to override all of the mouse events and call the base control's events with rotated coordinates.
You can get halfway there by calling the DrawToBitmap method on your panel, then rotating the bitmap and displaying it e.g. in a PictureBox:
var bitmap = new Bitmap(panel.Width, panel.Height);
panel.DrawToBitmap(bitmap, new Rectangle(Point.Empty, panel.Size));
bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
var pictureBox = new PictureBox();
pictureBox.Location = panel.Location;
pictureBox.SizeMode = PictureBoxSizeMode.AutoSize;
pictureBox.Image = bitmap;
Controls.Remove(panel);
Controls.Add(pictureBox);
Rotation angles other than 90-degree increments are also possible, if you draw the bitmap into another bitmap using GDI:
var bitmap2 = new Bitmap(bmp.Width + 75, bmp.Height + 100);
var graphics = Graphics.FromImage(bmp2);
graphics.TranslateTransform(bitmap2.Width / 2, bitmap2.Height / 2);
graphics.RotateTransform(-15f);
graphics.TranslateTransform(-bitmap.Width / 2, -bitmap.Height / 2);
graphics.DrawImageUnscaled(bitmap, Point.Empty);
graphics.Dispose();
The problem of course is that you're only displaying an image of your panel, and not the panel itself, so it's no longer possible to interact with the controls inside.
That could probably be done as well, but you would have to mess with window messages, which gets quite a bit more complicated. Depending on your needs you might also be able to get away with handling click and key events on the PictureBox, manipulating the controls in the panel, and then updating the image.

Resources