I am working on an application witch requires me to force the main window to have a locked aspect ratio. I found this snippet here. It works, but the window flickers while being re-sized.
float _currentRatio;
bool _lockRatio=false;
public bool LockRatio
{
get{ return _lockRatio; }
set{ _lockRatio=value; }
}
protected override void OnSizeChanged(EventArgs e)
{
if(!_lockRatio)
this._currentRatio=(float)this.Height/(float)this.Width;
else
this.Height=(int)(this.Width*this._currentRatio);
base.OnSizeChanged (e);
}
Is there any way of avoiding that flicker?
Related
I have a background thread, which renders some image to WriteableBitmap.
I'm making a custom control, which will use this writeablebitmap and update every frame to make animation.
Unfortunately, now forcing this control to InvalidateVisual().
That worked, but the performance is pretty bad.
On the main window I subscribed to Renderer.SceneInvalidated to log some framerates.
this.Renderer.SceneInvalidated += (s, e) =>
{
_logs.Add((DateTime.Now - _prev).Ticks);
_prev = DateTime.Now;
};
And found that about 7%-10% frames are rendered in 30ms, and others 90% in 16ms.
So I'm looking for a better performance solution with this problem.
You can utilize custom drawing operation API with direct access to SKCanvas on the render thread.
public class CustomSkiaPage : Control
{
public CustomSkiaPage()
{
ClipToBounds = true;
}
class CustomDrawOp : ICustomDrawOperation
{
public CustomDrawOp(Rect bounds)
{
Bounds = bounds;
}
public void Dispose()
{
// No-op in this example
}
public Rect Bounds { get; }
public bool HitTest(Point p) => false;
public bool Equals(ICustomDrawOperation other) => false;
public void Render(IDrawingContextImpl context)
{
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
// draw stuff
}
}
public override void Render(DrawingContext context)
{
context.Custom(new CustomDrawOp(new Rect(0, 0, Bounds.Width, Bounds.Height)));
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
}
}
I needed functionality that doesn't exist in the standard ComboBox, so I wrote my own from a TextBox and a form. When the user types in the TextBox, it shows a dropdown as a separate form.
Here's some of the relevant code:
internal class FilteredDropDown : Form
{
public Control OwnerControl { get; set; }
public bool CloseOnLostFocus { get; set; }
protected override OnLostFocus(EventArgs e)
{
if (CloseOnLostFocus && !OwnerControl.IsFocused)
this.Close();
}
protected override OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e)
// highlight the moused over item in the list
}
...
}
public class FilteredCombo : TextBox
{
private FilteredDropDown dropDown;
public FilteredCombo()
{
dropDown = new FilteredDropDown();
dropDown.OwnerControl = this;
}
public void ShowDropDown()
{
if (dropDown.Visible)
return;
dropDown.RefreshFilter();
var loc = PointToScreen(new Point(0, this.Height));
dropDown.Location = loc;
dropDown.CloseOnLostFocus = false;
int selectionStart = this.SelectionStart;
int selectionLength = this.SelectionLength;
dropDown.Show(this);
this.Focus();
this.SelectionStart = selectionStart;
this.SelectionLength = selectionLength;
dropDown.CloseOnLostFocus = false;
}
protected override OnLostFocus(EventArgs e)
{
if (dropDown.Visible && !dropDown.ContainsFocus())
dropDown.Close();
}
protected override OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
ShowDropDown();
}
...
}
There's obviously a whole lot more code than that to deal with all kinds of stuff irrelevent to my question.
The problem is when I put the FilteredCombo on a modal dialog. Somehow the FilteredDropDown form doesn't receive mouse events at all when it is parented by a modal dialog.
I've read something about WinForms filtering out events on all except the current modal dialog, I suspect that is what's going on, but I have no ideas of how to fix it. Is there some way to get the mouse up/down/move/click/etc. events to work when parented by a model dialog?
I had to go digging through the ShowDialog source code, and I found that it calls user32.dll EnableWindow(Handle, false) on all the windows except the shown one. The problem was that the FilteredDropDown already existed by the time the ShowDialog() method got called. I discovered two different ways to fix this:
Don't allow the DropDown to be shown until the parent form is shown. This is a bit trickier to guarantee, so I also implemented the second way.
Re-enable the DropDown window when it is made visible:
[DllImport("user32.dll")]
private static extern bool EnableWindow(IntPtr hWnd, bool enable);
protected override void OnVisibleChanged(EventArg e)
{
base.OnVisibleChanged(e);
if (this.Visible)
{
EnableWindow(this.Handle, true);
}
}
I've a Panel which fills the parent Form.
And I used a Timer to capture screen ,
and set the screenshot as background image of Panel periodically.
However, it runs into crazy flickering. What can I do to solve it?
//Part of code
public partial class Form1 : Form
{
DxScreenCapture sc = new DxScreenCapture();
public Form1()
{
InitializeComponent();
panelMain.BackgroundImageLayout = ImageLayout.Zoom;
}
private void Form1_Load(object sender, EventArgs e)
{
}
void RefreshScreen()
{
Surface s = sc.CaptureScreen();
DataStream ds = Surface.ToStream(s, ImageFileFormat.Bmp);
panelMain.BackgroundImage = Image.FromStream(ds);
s.Dispose();
}
private void timer1_Tick(object sender, EventArgs e)
{
RefreshScreen();
}
}
Try using a double buffered panel. Inherit panel, set DoubleBuffered to true and use that panel instead of default panel:
namespace XXX
{
/// <summary>
/// A panel which redraws its surface using a secondary buffer to reduce or prevent flicker.
/// </summary>
public class PanelDoubleBuffered : System.Windows.Forms.Panel
{
public PanelDoubleBuffered()
: base()
{
this.DoubleBuffered = true;
}
}
}
EDIT
Additionally I want to encourage you to take care a little more about the resources you use. Whenever an object implements the IDisposable interface - dispose the object when not needed any more. This is very important when dealing with unmanaged resources, such as streams!
void RefreshScreen()
{
using (Surface s = sc.CaptureScreen())
{
using (DataStream ds = Surface.ToStream(s, ImageFileFormat.Bmp))
{
Image oldBgImage = panelMain.BackgroundImage;
panelMain.BackgroundImage = Image.FromStream(ds);
if (oldBgImage != null)
oldBgImage.Dispose();
}
}
}
There is actually an easier solution in Visual Studio that requires no code!
If you go to Solution Explorer and then double click on your form (Form1) there will be a list that pops up (If it does not pop up you just have to right click on your form and go to Properties and double click again). Then, go to DoubleBuffered and change it to True.
I found the answer by myself from other site. It sets some ControlStyles on the panel like the following code. And no flickering any more.
class SomePanel : Panel
{
public SomePanel()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.UserPaint, true);
}
}
This is worked for me , Try this
protected override CreateParams CreateParams
{
get
{
CreateParams handleParms = base.CreateParams;
handleParms.ExStyle |= 0x02000000;
return handleParms;
}
}
I have to disable the OnPaintBackground on my TableLayoutPanel to remove flickering caused from the background being drawn first(because I am drawing on the TLP with the paint method, and yes I need a TLP because it contains many controls for a purpose). So my code is as follows:
public static bool FlickerPanel = false;
public class FlickerTableLayoutPanel : TableLayoutPanel
{
protected override void OnPaintBackground(PaintEventArgs e)
{
if (FlickerPanel)
base.OnPaintBackground(e);
}
}
Then in my paint method I have it draw it's own background. So during runtime it is fine.
Edit: I discovered the root of the problem. By overriding the OnPaintBackground it disables whatever code is making the designer draw the background. If I remove the override all together it doesn't have the graphical glitch.
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
}
Even this above code disabled the Design view rendering and causes graphical glitches. Any help much appreciated!
I also had problems detecting whether my form was in design mode. I solved it as follows:
Firstly, I had to write an IsDesignMode() method:
public static bool IsDesignMode(Control control)
{
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime) // Initial quick check.
{
return true;
}
while (control != null)
{
System.ComponentModel.ISite site = control.Site;
if ((site != null) && (site.DesignMode))
return true;
control = control.Parent;
}
return false;
}
I put that method in a shared library assembly (namespace "Windows.Forms.Utilities" in the example below), since I use it in a lot of projects.
Then for each user or custom control where I need to know if it's in design mode, I have to add a private bool _isDesignMode field and override OnHandleCreated() as follows:
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
_isDesignMode = Windows.Forms.Utilities.IsDesignMode(this);
}
Then I can use _isDesignMode wherever I need to.
You could surround the code in the OnPaintBackground method with an if statement to detect if your in design time like this:
if (System.ComponentModel.LicenseManager.UsageMode ==
System.ComponentModel.LicenseUsageMode.Designtime)
I just wanted to contribute my slight upgrade to the solution that #matthew-watson provided. As an extension method, the syntax becomes a bit more pleasing to read.
public static partial class ExtensionMethods
{
public static bool IsInDesignMode(this Control source)
{
var result = LicenseManager.UsageMode == LicenseUsageMode.Designtime;
while (result == false && source != null)
{
result = source.Site != null && source.Site.DesignMode;
source = source.Parent;
}
return result;
}
}
and used like this
protected override void OnPaintBackground(PaintEventArgs pevent)
{
if (this.IsInDesignMode())
{
base.OnPaintBackground(pevent);
}
}
just do nothing onPaintBackground() function. Its prevent backgroundImage to be drawn and should fix the flickering.
protected override void OnPaintBackground(PaintEventArgs e)
{
}
I'm learning how to write custom control and manage and raise events.
I'have create a pointless custom control, a line you can add on main and decide size and color. I've made also an event to replace OnClick (I'm just learning) and I've called it MyClick. Here's the code
public class EditedLine : UserControl
{
public delegate void LineClickEventHandler(object sender, EditedLineClickEventArgs e);
[Description("Occurs when control is clicked")]
public event LineClickEventHandler MyClick;
private Color _color;
private float _size;
public EditedLine()
{
_color = Color.Black;
_size = 3;
}
public EditedLine(Color color, float size)
{
_color = color;
_size = size;
}
public Color LineColor
{
get { return _color; }
set { _color = value; }
}
public float LineSize
{
get { return _size; }
set { _size = value; }
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawLine(new Pen(_color, _size), new Point(0, 0), new Point(this.Size.Width, 0));
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
OnLineClick(new EditedLineClickEventArgs());
}
protected virtual void OnLineClick(EditedLineClickEventArgs e)
{
if (MyClick != null)
{
MyClick(this, e);
}
}
}
}
luckly everythings is working. I can see my control in the control toolbox and I can add to my project, even the event is working.
But, if inside MyClick i try to change the color of the Line, nothing happens.
I have to use this Proprieties intead of the ones before:
public Color LineColor
{
get { return _color; }
set { _color = value; this.Refresh(); }
}
public float LineSize
{
get { return _size; }
set { _size = value; this.Refresh(); }
}
So I'm asking myself if there is a better way to refresh the control. If I'm redrawing a single line is easy to be done, but if it is a more complex control? I thought to do a 'Redraw' private method but I don't know how to access the 'Graphic' object of the EditedLine istance. Is 'INotifyPropertyChanged' useful? But the 'Graphic' still remains my biggest problem
If you are talking about more complex control, then consider to change Refresh to Invalidate to invalidate specific regions of your UserControl.
To get Graphics object just call CreateGraphics method.