Overriding OnPaintBackground makes the Designer view misbehave - c#

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)
{
}

Related

how to call Resize Event in C#

I'm trying to create new Button with custom event. It's new for me. I'm trying to call "Resize". I wanna create "switch" like in android.
I'm trying to do this like other existing controls. I've been doing this for 2 days and i still have nothing. I belive that you will able to help me :)
Here is my code:
public abstract class SwitchBase : Control
{
private Button first;
private Button second;
public SwitchBase()
{
InitializeMySwitch();
}
private void InitializeMySwitch()
{
Controls.Add(first = new Button());
Controls.Add(second = new Button());
//first
first.Text = "first";
//second
second.Text = "second";
second.Location = new System.Drawing.Point(first.Location.X + first.Width, first.Location.Y);
}
public delegate void ChangedEventHandler(object source, EventArgs args);
public event ChangedEventHandler Changed;
protected virtual void OnSwitchChanged()
{
if (Changed != null)
Changed(this, EventArgs.Empty);
}
public delegate void ResizeEventHandler(object source, EventArgs args);
public event ResizeEventHandler Resize;
protected virtual void OnResize()
{
Resize(this, EventArgs.Empty);
}
}
public class Switch : SwitchBase
{
public Switch()
{
}
protected override void OnSwitchChanged()
{
base.OnSwitchChanged();
}
protected override void OnResize()
{
base.OnResize();
}
}
In another button I change the size of my switch
From reading your code, I gather that by "call Resize" you mean to raise the event. What you are doing is correct... although it should be noted that by the default event implementation, it will be null if there are no subscribers...
In fact, another thread could be unsubscribing behind your back. Because of that the advice is to take a copy.
You can do that as follows:
var resize = Resize;
if (resize != null)
{
resize(this, EventArgs.Empty)
}
It should be noted that the above code will call the subscribers to the event, but will not cause the cotrol to resize. If what you want is to change the size of your control, then do that:
this.Size = new Size(400, 200);
Or:
this.Width = 400;
this.Height = 200;
Note: I don't know what Control class you are using. In particular, if it were System.Windows.Forms.Control it already has a Resize event, and thus you won't be defining your own. Chances are you are using a Control class that doesn't even have Size or Width and Height.
Edit: System.Web.UI.Control doesn't have Resize, nor Size or Width and Height. But System.Windows.Controls.Control has Width and Height even thought it doesn't have Resize.

RichTextBox blocks DragDrop event

I have DragDrop and DragEnter events on my SplitContainer.Panel:
splitContainer.Panel.DragDrop += new System.Windows.Forms.DragEventHandler(this.splitContainerPanelDragDrop);
splitContainer.Panel.DragEnter += new System.Windows.Forms.DragEventHandler(this.splitContainerPanelDragEnter);
It works perfect with every control inside SplitContainer.Panel except RichTextBox controls.
How it looks like:
So DragDrop/DragEnter works perfectly in every control inside SplitContainer except controls which is marked yellow color.
What I tried:
1) Set
RichTextBox.AllowDrop = false;
So I even DragEnter is unavailable with "action is not allowed" cursor.
2) Set
RichTextBox.AllowDrop = true;
After this cursor is ok, but it doesnt work because expects additional DragEventHandler in other case it doesnt work.
3) Set
RichTextBox.EnableAutoDragDrop=false;
RichTextBox.AllowDrop=true;
Same result as 2) variant.
I dont want to set DragDrop/DragEnter event for every RichTextBox inside SplitContainer because inside FlowLayoutPanel they are created dynamically.
The question is: is there any method like e.PreventDefault analog in C#? Or what can I do except setting events for every RichTextBox to make it work?
This worked for me
I created 2 custom controls
Custom SplitControl
public partial class SplitControlCustom : SplitContainer
{
public SplitControlCustom()
{
InitializeComponent();
}
public void ForceDrageDrop(DragEventArgs eventArgs)
{
OnDragDrop(eventArgs);
}
public void ForceDragEnter(DragEventArgs eventArgs)
{
OnDragEnter(eventArgs);
}
}
Custom RichTextBox
public partial class RichTextBoxCustom : RichTextBox
{
public RichTextBoxCustom()
{
InitializeComponent();
this.AllowDrop = true;
}
protected override void OnDragEnter(DragEventArgs drgevent)
{
SplitControlCustom parentSplitControl = Parent.Parent as SplitControlCustom;
if (parentSplitControl != null)
{
parentSplitControl.ForceDragEnter(drgevent);
}
}
protected override void OnDragDrop(DragEventArgs drgevent)
{
SplitControlCustom parentSplitControl = Parent.Parent as SplitControlCustom;
if (parentSplitControl != null)
{
parentSplitControl.ForceDrageDrop(drgevent);
}
}
}
Please let me know if it worked
I don't see how you can make this work directly. But then, since you are already willing to add a few lines of code while generating the controls, why not add the necessary events via a few lines of Lambda..:
Let's assume you have just created a RichTextBox and are ready to add it to some Controls collection..:
RichTextBox richTextBox = new RichTextBox ();
...
richTextBox.AllowDrop = true;
richTextBox.DragEnter += (ss, ee) => { ee.Effect = DragDropEffects.Copy; };
richTextBox.DragOver += (ss, ee) => { ee.Effect = DragDropEffects.Copy; };
richTextBox.DragDrop += (ss, ee)
=> { splitContainer.Panel_DragDrop(splitContainer.Panel, ee); };
The first two lambdas set the effect to copy without any checks; of course you will want to add those and pick the appropriate effect.
The third lambda passes the DragEventArgs on the the DragDrop event of the containing panel, so now the RTB is actually 'D&D-through' ..
Just create a custom RichTextBox and override it's DragDrop Events.
public class CustomRichTextBox : RichTextBox
{
#region Methods
#region Overrides
protected override void OnDragEnter(DragEventArgs e)
{
// base.OnDragEnter(e);
}
protected override void OnDragOver(DragEventArgs e)
{
// base.OnDragOver(e);
}
protected override void OnDragLeave(DragEventArgs e)
{
// base.OnDragLeave(e);
}
protected override void OnDrop(DragEventArgs e)
{
// base.OnDrop(e);
}
#endregion
#endregion
}
For some reason RichTextBoxes seem to handle all DragDrop events by default.
In WPF the events will propagate till it gets to the control that expects these events. I'm not sure about WinForms though.
This is what resolved this issue for me.
I had these two events defined, which should have been good enough
MyRichTextBox.DragEnter += MyRichTextBox_DragEnter;
MyRichTextBox.DragDrop += MyRichTextBox_DragDrop;
I found that this one is also apparently needed when using a RichTextBox embedded in
certain controls.
MyRichTextBox.DragOver += MyRichTextBox_DragOver;
private void MyRichTextBox_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}

WinForms control's child form doesn't respond to mouse events when control is on modal dialog

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);
}
}

Locking aspect ratio causes flickering of the window

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?

WPF treeview itemselected moves incorrectly when deleting an item

I have a treeview bound to an object tree. When I remove an object from the object tree, it is removed correctly from the tree view, but the treeview's default behaviour is to jump the selecteditem up to the deleted item's parent node. How can I change this so it jumps to the next item instead?
EDIT:
I updated my code with Aviad's suggestion. Here is my code..
public class ModifiedTreeView : TreeView
{
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (e.Action == NotifyCollectionChangedAction.Remove)
{
if (e.OldStartingIndex - 1 > 0)
{
ModifiedTreeViewItem item =
this.ItemContainerGenerator.ContainerFromIndex(
e.OldStartingIndex - 2) as ModifiedTreeViewItem;
item.IsSelected = true;
}
}
}
protected override DependencyObject GetContainerForItemOverride()
{
return new ModifiedTreeViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is ModifiedTreeViewItem;
}
}
public class ModifiedTreeViewItem : TreeViewItem
{
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (e.Action == NotifyCollectionChangedAction.Remove)
{
if (e.OldStartingIndex > 0)
{
ModifiedTreeViewItem item =
this.ItemContainerGenerator.ContainerFromIndex(
e.OldStartingIndex - 1) as ModifiedTreeViewItem;
item.IsSelected = true;
}
}
}
protected override DependencyObject GetContainerForItemOverride()
{
return new ModifiedTreeViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is ModifiedTreeViewItem;
}
}
The code above does not work unless I debug it, or in some way slow down the OnItemsChanged method. For example, if I put a thread.sleep(500) at the bottom of the OnItemsChanged method, it works, otherwise it does not. Any idea what I'm doing wrong? This is really strange.
The behavior you mention is controlled by a virtual method in the Selector class called OnItemsChanged (reference: Selector.OnItemsChanged Method) - In order to modify it, you should derive from TreeView and override that function. You might use reflector to base your implementation on the existing implementation, although it's pretty straightforward.
Here's the code for the treeview override TreeView.OnItemsChanged extracted using reflector:
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Reset:
if ((this.SelectedItem == null) || this.IsSelectedContainerHookedUp)
{
break;
}
this.SelectFirstItem();
return;
case NotifyCollectionChangedAction.Replace:
{
object selectedItem = this.SelectedItem;
if ((selectedItem == null) || !selectedItem.Equals(e.OldItems[0]))
{
break;
}
this.ChangeSelection(selectedItem, this._selectedContainer, false);
return;
}
default:
throw new NotSupportedException(SR.Get("UnexpectedCollectionChangeAction", new object[] { e.Action }));
}
}
Alternatively, you might hook into the collection NotifyCollectionChanged event from one of your code-behind classes and explicitly change the current selection before the event reaches the TreeView (I'm not sure of this solution though because I am not sure of the order in which event delegates are called - the TreeView might get to process the event before you do - but it might work).
Original answer
In my original answer I guessed that you may be encountering a bug in WPF and gave a generic workaround for this kind of situation, which was to replace item.IsSelected = true; with:
Disptacher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
{
item.IsSelected = true;
}));
I explained that the reason this kind of workaround does the trick 90% of the time is that it delays the selection until almost all current operations have finished processing.
When I actually tried the code you posted in your other question I discovered that it was indeed a bug in WPF but found a more direct and reliable workaround. I'll explain how I diagnosed the problem and then describe the workaround.
Diagnosis
I added a SelectedItemChanged handler with a breakpoint in it, and looked at the stack trace. This made it obvious where the problem lies. Here are selected portions of the stack trace:
...
System.Windows.Controls.TreeView.ChangeSelection
...
System.Windows.Controls.TreeViewItem.OnGotFocus
...
System.Windows.Input.FocusManager.SetFocusedElement
System.Windows.Input.KeyboardNavigation.UpdateFocusedElement
System.Windows.FrameworkElement.OnGotKeyboardFocus
System.Windows.Input.KeyboardFocusChangedEventArgs.InvokeEventHandler
...
System.Windows.Input.InputManager.ProcessStagingArea
System.Windows.Input.InputManager.ProcessInput
System.Windows.Input.KeyboardDevice.ChangeFocus
System.Windows.Input.KeyboardDevice.TryChangeFocus
System.Windows.Input.KeyboardDevice.Focus
System.Windows.Input.KeyboardDevice.ReevaluateFocusCallback
...
As you can see, KeyboardDevice has a ReevaluateFocusCallback private or internal method which changes the focus to the parent of the deleted TreeViewItem. This causes a GotFocus event which causes the parent item to be selected. This all happens in the background after your event handler returns.
Solution
Normally in this case I would tell you to just manually .Focus() the TreeViewItem you are selecting. That is difficult here because in a TreeView there is no easy way to get from an arbitrary data item to the corresponding container (there are separate ItemContainerGenerators at each level).
So I think your best solution is to force the focus to the parent node (just where you don't want it to end up), then set IsSelected in the child's data. That way the input manager will never decide it needs to move the focus on its own: It will find the focus already set to a valid IInputElement.
Here is some code to do that:
if(child != null)
{
SomeObject parent = child.Parent;
// Find the currently focused element in the TreeView's focus scope
DependencyObject focused =
FocusManager.GetFocusedElement(
FocusManager.GetFocusScope(tv)) as DependencyObject;
// Scan up the VisualTree to find the TreeViewItem for the parent
var parentContainer = (
from element in GetVisualAncestorsOfType<FrameworkElement>(focused)
where (element is TreeViewItem && element.DataContext == parent)
|| element is TreeView
select element
).FirstOrDefault();
parent.Children.Remove(child);
if(parent.Children.Count > 0)
{
// Before selecting child, first focus parent's container
if(parentContainer!=null) parentContainer.Focus();
parent.Children[0].IsSelected = true;
}
}
This also requires this helper method:
private IEnumerable<T> GetVisualAncestorsOfType<T>(DependencyObject obj) where T:DependencyObject
{
for(; obj!=null; obj = VisualTreeHelper.GetParent(obj))
if(obj is T)
yield return (T)obj;
}
This should be more reliable than using Dispatcher.BeginInvoke because it will work around this particular problem without making any assumptions about input queue ordering, Dispatcher priorities, and so forth.
This works for me (thanks to investigations provided above)
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (e.Action == NotifyCollectionChangedAction.Remove)
{
Focus();
}
}
According to the answer provided by #Kirill I think the correct answer to this specific question would be the following code added to a class derived from TreeView.
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove && SelectedItem != null)
{
var index = Items.IndexOf(SelectedItem);
if (index + 1 < Items.Count)
{
var item = Items.GetItemAt(index + 1) as TreeViewItem;
if (item != null)
{
item.IsSelected = true;
}
}
}
}
Based on the answers above, here's the solution that worked for me (it has fixed various other problems as well, such as the loss of focus after selecting an item via model etc.)
Note the OnSelected override (scroll all the way down) which actually did the trick.
This was compiled in VS2015 for Net 3.5.
using System.Windows;
using System.Windows.Controls;
using System.Collections.Specialized;
namespace WPF
{
public partial class TreeViewEx : TreeView
{
#region Overrides
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeViewItemEx();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeViewItemEx;
}
#endregion
}
public partial class TreeViewItemEx : TreeViewItem
{
#region Overrides
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeViewItemEx();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeViewItemEx;
}
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
if (HasItems)
{
int newIndex = e.OldStartingIndex;
if (newIndex >= Items.Count)
newIndex = Items.Count - 1;
TreeViewItemEx item = ItemContainerGenerator.ContainerFromIndex(newIndex) as TreeViewItemEx;
item.IsSelected = true;
}
else
base.OnItemsChanged(e);
break;
default:
base.OnItemsChanged(e);
break;
}
}
protected override void OnSelected(RoutedEventArgs e)
{
base.OnSelected(e);
Focus();
}
#endregion
}
}

Categories