SL 4 -- Force redraw of visual tree - silverlight

Our application has a number of objects on a canvas; the canvas is contained in a scroll viewer. We also have a slider control and some buttons, always centered at the top of the window.
I am trying to print the application by capturing a bitmap of the app, but without any 'decorations' -- slider, buttons, or scroll bars.
_scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
_scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
var s = xSlider;
s.Visibility = Visibility.Collapsed;
var b = xPlusButton;
b.Visibility = Visibility.Collapsed;
b = xMinusButton;
b.Visibility = Visibility.Collapsed;
b = xButton;
b.Visibility = Visibility.Collapsed;
The slider and buttons are hidden, as expected, but the scrollbars are not.
I suspect the application needs to redraw the layout in order to hide the scrollbars. Is there a way to make that happen? This is made more complicated by the fact that the print operation in SL 4 must be initiated by a UI gesture; there is no way (AFAIK) to initiate programatically, so this redraw must happen in one of the PrintDocument event handlers.
Thanks for any suggestions....

Try following,
canvas.InvalidateMeasure();
canvas.InvalidateArrange();
You can alternatively use WritableBitmap to capture runtime image and send image to print document if in case print document is ignoring render transform.
Also if you are using WritableBitmap to capture the element then you should give RenderTransform as second argument. Can you post your code to capture screen?

In addition to the InvalidateMeasure and InvalidateArrange methods, as suggested by Akash, you can try the UpdateLayout method.
The two invalidate methods will mark the control's measure or arrange as needing to be executed again, but won't necessarily do it immediately. The UpdateLayout will force it to execute some updates immediately.
It's a bit of a black box, so you may need to invalidate then call UpdateLayout. Sometimes you may just need to call UpdateLayout.

Related

How to Set the Background of WPF Control and Ensure Rendering Immediately When Visible?

I have a WPF application that takes a screenshot of the contents of a WindowsFormsHost control and applies it to the background of a Grid control. The Grid acts as a transition screen from the WindowsFormsHost to other WPF controls. This is done to make a smooth transition effect and to avoid air space issues. I first capture the WinFormsHost control image as a bitmap and apply it to the Grid background. Then programmatically change the visibility of the transition Grid to visible. Then do an opacity animation to smoothly show another control. It works perfectly about 70 to 90 percent of the time depending on what computer I test the application on. The problem is that the transition Grid background is not being rendered fast enough or at the correct time. So that occasionally I am seeing a screenshot from a previous transition which does not match the current screen image at transition time.
If I could somehow ensure that the transition image was drawn before the Grid is made visible, it would work everytime. I just cannot see how to do this. The application seems to always wait until the last minute to do any rendering.
I have tried to force rendering with Dispatcher.Invoke() and Dispatcher.BeginInvoke() methods. I have also tried to delay the time when the Grid is made visible with a Dispatcher Timer, but no matter how much time there is between the call setting the background image and setting the visibility, the Grid does not always update. I have also tried things like InvalidateVisual() with no luck.
After looking at many examples into problems involving WPF's rending and UI threads, I made several attempts to correct the problem...
Attempt 1 - Works poorly, but attempt works better with a line of code that first sets background to nothing or black:
TransitionScreen.Visibility = Visibility.Visible
TransitionScreen.Background = Nothing
TransitionScreen.Background = New ImageBrush(BitmapToImageSource(GrabScreenshot(ScreenWidth, ScreenHeight)))
State = "WPFControlMode"
SwitchState()
Attempt 2 - Best results so far. Works fair to well depending on computer. Increasing the counter value does not improve results. The appearance of the TransitionScreen will be delayed with a larger counter value, but will have the same chance of showing a previous screenshot (about 1 in 5 times on my laptop):
Case "Transition"
'Grab Screen Shot and apply to transition screen...'
TransitionScreen.Visibility = Visibility.Visible
TransitionScreen.Background = Nothing
TransitionScreen.Background = New ImageBrush(BitmapToImageSource(GrabScreenshot(ScreenWidth, ScreenHeight)))
End Select
'The following code is inside a loop...'
If State = "Transition" Then
TransitionCounter += 1
If TransitionCounter = 25 Then
TransitionCounter = 0
TransitionScreen.Visibility = Visibility.Visible
TransitionScreen.Background = Nothing
TransitionScreen.Background = New ImageBrush(BitmapToImageSource(GrabScreenshot(ScreenWidth, ScreenHeight)))
State = "WPFControlMode"
SwitchState()
End If
End If
Attempt 3, 4, 5, ... - Attempts try to force rendering of control by working through Dispatcher. These attempts do not appear to help and often make the problem worse. They have been used in combination with both attempts 1 and 2.
Case "Transition"
'Grab Screen Shot and apply to transition screen...'
TransitionScreen.Visibility = Visibility.Visible
TransitionScreen.Background = Nothing
TransitionScreen.Background = New ImageBrush(BitmapToImageSource(GrabScreenshot(ScreenWidth, ScreenHeight)))
FlushWindowsMessageQueue()
End Select
'Tried many variations of the following procedure. Every dispatcher priority with both BeginInvoke and Invoke methods, as well as calling the methods through Application and TransitionScreen objects.'
Private Sub FlushWindowsMessageQueue()
Application.Current.Dispatcher.Invoke( _
New Action(AddressOf DummySub), _
DispatcherPriority.Background, _
New Object() {})
End Sub
Private Sub DummySub()
End Sub
There were more attempts also, but this should give you an idea of what I have tried. This is a real puzzle. This is the last major problem with a fairly involved project, and I am pretty much invested in the transition screen at this point. If you can think of anything, I will greatly appreciate it. Thanks.
I did come up with a work around for this problem. I do a transition animation before I take the screenshot. The transition animation results in a lower resolution image than the control would normally show. Then I take the screenshot of the lower resolution image which can be applied to the transition grid control, after it is made visible, fast enough that there is never a flicker. Then when I want to return to the WinFormsHost control, I run through this process in reverse. My application now works without flicker and run smoothly on most computers I have tried. On some computers with large monitors, there is sometimes a slight delay when rendering the transition screen, but still no flicker. The delay is about half a second, and I assume it has to do with the larger area that transition image must be rendered to.
Also, the above was done in combination with method/attempt #2. The flicker still occurs when in combination with attempt #1.

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

Can't consistently maximize WPF window

I have a minimized WPF window. I click the item in the taskbar to maximize. It makes a little audio ding, then I try again, and again. Usually about the third try it will maximize. What could cause it to refuse my initial maximize attempt?
One possibility is that you have some code that's changing the value of the ResizeMode property to NoResize.
See this page for more: http://msdn.microsoft.com/en-us/library/ms748948.aspx
Second, you might be overriding OnStateChanged and not calling base.OnStateChanged() consistently.
Third, you may have something hogging the UI's thread during your first attempts. Once that task--whatever it is--stops blocking then WPF can repaint the window in restored/maximized state.
I had a similar problem when trying to manually maximize a custom window.
The solution was to put the next code in my maximize button...
this.SizeToContent = System.Windows.SizeToContent.Manual;
this.MaxWidth = double.PositiveInfinity;
this.MaxHeight = double.PositiveInfinity;
this.Width = double.NaN;
this.Height = double.NaN;
this.WindowState = WindowState.Maximized;
Where 'this' referes to the Window.

Form height problem when FormBorderStyle is NONE

I have a borderless form (FormBorderStyle = None) with the height of 23 pixels (set in the designer)
When .NET draws my form at runtime - it draws it 38 pixels high (it adds the height of a title-bar for some reason).
MessageBox.Show(this.Height.ToString()); //this shows 38!! why?
To work it around I have to set "Height = 23;" in the Form_Load event.
private void MyForm_Load(object sender, EventArgs e)
{
this.Height = 23; //workaround. wtf??
}
You can try this yourself in Visual Studio 2010 (Winforms App, target Framework - 2.0).
Wtf?
Yeah, it is a bug, of sorts. Note how in the designer you set the size of the form with the Width and Height properties. Those properties include the size of the borders and the title bar. That's a problem however, your form may run on a machine where the user has increased, say, the title bar font size. That then would reduce the size of the window's client area. Or in other words, the form's ClientSize property would change on that machine. Leaving less room for the controls and messing up the design of your form pretty badly.
There's code inside the Form class that runs after the Handle is created, right before the Load event runs. It recalculates the Size of the form, using the same ClientSize you had on your machine. Now everything is good, the Height of the form won't match the one you set in the designer but the form otherwise looks the same and the layout of the controls is identical.
That same code also ensures that the window doesn't get too small. And that's where it falls over, it doesn't pay enough attention to the FormBorderStyle property. Clipping the height to the title bar size plus the client area height, as you found out. It also prevents the form getting too narrow, trying to make sure that the icon and min/max/close buttons are always visible. Even if you don't have any.
The workaround is to change the ClientSize after this code runs, the OnLoad override or Load event handler is the right place for that. Beware that if you hard-code the form size like this then you should also set the AutoScaleMode property to None. Make sure that this doesn't cause trouble on a machine that has a different DPI setting.

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