I have a custom control that inherits from TreeView. In this CustomTreeView, I handle the OnNodeMouseClick event to perform some process before changing the node.Checked state as the user would expect it:
public class CustomTreeView : TreeView {
// Constructor...
protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e) {
base.OnNodeMouseClick(e);
// Do something...
e.Node.Checked = !e.Node.Checked;
}
}
My problem is when the developer subscribes to the AfterCheck event on a CustomTreeView, the value of e.Action is always TreeViewAction.Unknown (because the checked state of the node is changed in the code), whereas the developer is waiting for TreeViewAction.ByMouse:
public partial class Form1: Form {
private void customTreeView1_AfterCheck(object sender, TreeViewEventArgs e) {
// e.Action == TreeViewAction.Unknown
// [developer] The user clicked on the node, it should be
// TreeViewAction.ByMouse!?
}
}
What I would like to do is disable the AfterCheck event from firing and call it myself in my CustomTreeView class, that way I would be able to pass parameters with TreeViewAction equal to ByMouse. Something like that:
public class CustomTreeView : TreeView {
// Constructor...
protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e) {
base.OnNodeMouseClick(e);
// Do something...
// Prevent all AfterCheck events from firing, just for a moment
// ??
e.Node.Checked = !e.Node.Checked;
// Allow AfterCheck events to fire
// ??
// Call myself the AfterCheck event
base.OnAfterCheck(new TreeViewEventArgs(e.Node, TreeViewAction.ByMouse));
}
}
Is this possible?
Sure, just override OnAfterCheck in CustomTreeView and it will work like you intend.
public class CustomTreeView : TreeView {
protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e) {
base.OnNodeMouseClick(e);
// your stuff
// Call myself the AfterCheck event
base.OnAfterCheck(new TreeViewEventArgs(e.Node, TreeViewAction.ByMouse));
}
protected override void OnAfterCheck(TreeViewEventArgs e)
{
// do nothing
}
}
Related
I have a class that inherits from ObservableCollection<T>. In that class I have a method that changes the collection internally and for which I'd like to suppress CollectionChanged events.
public class ContentBlockList : ObservableCollection<int> {
public void SomeMethod() {
var handlers = CollectionChanged.GetInvocationList();
foreach (NotifyCollectionChangedEventHandler handler in handlers) {
CollectionChanged -= handler;
}
// do stuff here
foreach (NotifyCollectionChangedEventHandler handler in handlers) {
CollectionChanged += handler;
}
}
}
Intuitively it seems like this should work since I'm accessing the event from within its containing object. Unfortunately, the compiler says
The event 'ObservableCollection.CollectionChanged' can only
appear on the left hand side of += or -=
I can get the code to work if I override both CollectionChanged and OnCollectionChanged(), essentially replacing the .NET versions with copies of my own. However, having to do something like that makes me suspicious that I'm ignoring some reason why doing this is a bad idea in the first place. Thanks for any thoughts on this.
As unsubscribing and re-subscribing to an event is a relatively (not really painful but I don't know how many subscribers there is likely to be) slow process I would recommend that you look into overriding both the the OnCollectionChanged and OnPropertyChanged methods for the base ObservableCollection.
So have something that resembles:
public class ContentBlockList : ObservableCollection<int>
{
private bool internallyUpdating;
public void SomeMethod()
{
this.internallyUpdating = true;
// Do Stuff (Add to base collection)
this.internallyUpdating = false;
this.OnPropertyChanged(new PropertyChangedEventArgs(#"Count");
this.OnPropertyChanged(new PropertyChangedEventArgs(#"Item[]");
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if(this.internallyUpdating)
{
return;
}
base.OnCollectionChanged(e);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(this.internallyUpdating)
{
return;
}
base.OnPropertyChanged(e);
}
}
What this allows for is the ability to suppress the events being raised while you update internally but doing this in a way that means you do not have to unsubscribe and resubscribe to events.
When adding to this collection normally (i.e. with contentBlockList.Add(1)) you'll fall straight through to calling the base event. But when you are trying to update internally you'll suppress these events until you have finished.
I'd say this is both more efficient in terms of performance but also much neater code than what you were looking at.
On a last note I'd also say that the NotifyCollectionChangedEventAction that you provide is Reset. You've probably done quite a big change to the collection and to handle it you'll want any subscriber to have to refresh their look on the collection, be it a control in a WPF view or even another class that uses the collection.
better use this :
public class ContentBlockList : ObservableCollection<int>
{
ContentBlockList()
{
this.CollectionChanged += ContentBlockList_CollectionChanged;
}
void ContentBlockList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
}
}
if you maintain your code try this
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ContentBlockList pp = new ContentBlockList();
pp.CollectionChanged += pp_CollectionChanged;
pp.CollectionChanged += pp_CollectionChanged1;
pp.Add(11112);
pp.SomeMethod();
}
void pp_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
}
void pp_CollectionChanged1(object sender, NotifyCollectionChangedEventArgs e)
{
}
}
public class ContentBlockList : ObservableCollection<int>
{
public void SomeMethod()
{
var handlers = CollectionChanged.GetInvocationList();
foreach (NotifyCollectionChangedEventHandler handler in handlers)
{
CollectionChanged -= handler;
}
// do stuff here
foreach (NotifyCollectionChangedEventHandler handler in handlers)
{
CollectionChanged += handler;
}
}
public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
}
As far as I understood you need to interrupt firing of CollectionChanged to do some work silently. So you can create boolean field like __FireCollectionChanged and then override OnCollectionChanged() to do:
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (__FireCollectionChanged)
base.OnCollectionChanged(e);
}
Then you can control whether event is fired by that boolean field.
And answering the actual question: you can't use the invocation list directly, because the event is not the delegate type field. It's basically just two methods add and remove for subscribe/unsubscribe behavior. Underlying delegate field is created behind the scenes and you typically don't want to use it.
I have a form, on this form is a flowlayoutpanel with multiple custom made TextBoxes
The form overrides the base methode Refresh(), to do some other things also.
Now I'm digging into the parent to eventueally come on the form and do the refresh
this.Parent.Parent.Parent.Refresh();
I want to re-use the control on other forms, so is there another way to do this?
And I know a While(true) is possible:
Boolean diggToParent = true;
var parent = this.Parent;
while (diggToParent)
{
if (parent.Parent != null)
{
parent = parent.Parent;
}
else
break;
}
parent.Refresh();
But is there a cleaner way to do this?
You can solve this by creating and raising an event that is handled by the parent form:
public class MyUserControl : UserControl
{
// ...
public event EventHandler RequestRefresh;
// Call this method whenever you want the parent to refresh
private void OnRequestRefresh()
{
if (RequestRefresh != null)
RequestRefresh(this, EventArgs.Empty);
}
}
In the parent form (or the container that should be refreshed), you add an event handler, e.g.
public class MyParentForm : Form
{
public MyParentForm()
{
InitializeComponent();
userCtrl.RequestRefresh += userCtrl_RequestRefresh;
}
// Do whatever the parent thinks is necessary to refresh.
public void userCtrl_RequestRefresh(object sender, EventArgs e)
{
Refresh();
}
// ...
}
This way the parent form can decide what to do when the user control requests a refresh. For details on events, see this link.
I have a WinForm with some numreicUpDown Controls, i want to know if the value has been incremented or decremented. the control fires the event value changed for both situations, and as far as i can understand the programm calls the methods UpButton and DownButton. Is there any other way to know how the value has been changed or do i have to do this with this methods(like firing eventor implementig my code in Up-Down-Button)
There is no standart way to do this.
I sugest to remember the old value and compare it with new one
decimal oldValue;
private void ValueChanged(object sender, EventArgs e)
{
if (numericUpDown.Value > oldValue)
{
}
else
{
}
oldValue = numericUpDown.Value;
}
Create your own control that overrides those UpButton and DownButton methods:
using System.Windows.Forms;
public class EnhancedNUD : NumericUpDown
{
public event EventHandler BeforeUpButtoning;
public event EventHandler BeforeDownButtoning;
public event EventHandler AfterUpButtoning;
public event EventHandler AfterDownButtoning;
public override void UpButton()
{
if (BeforeUpButtoning != null) BeforeUpButtoning.Invoke(this, new EventArgs());
//Do what you want here...
//Or comment out the line below and do your own thing
base.UpButton();
if (AfterUpButtoning != null) AfterUpButtoning.Invoke(this, new EventArgs());
}
public override void DownButton()
{
if (BeforeDownButtoning != null) BeforeDownButtoning.Invoke(this, new EventArgs());
//Do what you want here...
//Or comment out the line below and do your own thing
base.DownButton();
if (AfterDownButtoning != null) AfterDownButtoning.Invoke(this, new EventArgs());
}
}
Then when you implement the control on your form, you can hook up some of the events to let you know which button was clicked or key (up/down) hit.
Can someone tell me how can I have a feature in my UserControl, that can let the host windowsform know what is the control is doing?
For example my usercontrol has a filebrowser, and if user uses this file browser to open a file I want in the statusstrip bar of my form to write "Loading file(s)".
Will this require using events? if so, how can I have a single event inside usercontrol to report anything it does (then I guess I have to call this event in all methods in the usercontrol).
Simple
Yes, expose an event on the user control that the Form can subscribe to. You should use the standard event pattern:
class MyUserControl : UserControl
{
public event EventHandler<EventArgs> FileOpened;
protected virtual void OnFileOpened(EventArgs e)
{
EventHandler<EventArgs> handler = FileOpened;
if (handler != null)
handler(this, e);
}
}
Then when the file is opened you call OnFileOpened(EventArgs.Empty) which fires the event.
With custom EventArgs
Now the Form probably needs to know what file was opened. You could expose a property on the user control that the Form can use to find out, or you can provide that information in your event like so:
public class FileOpenedEventArgs : EventArgs
{
private string filename;
public FileOpenedEventArgs(string filename)
{
this.filename = filename;
}
public string Filename { get { return filename; } }
}
class MyUserControl : UserControl
{
public event EventHandler<FileOpenedEventArgs> FileOpened;
protected virtual void OnFileOpened(FileOpenedEventArgs e)
{
EventHandler<FileOpenedEventArgs> handler = FileOpened;
if (handler != null)
handler(this, e);
}
}
Then you fire the event with OnFileOpened(new FileOpenedEventArgs(filename)).
Optimal
When you create an event handler public event delegate Name;, you are allocating storage for the delegate on your object. Objects (especially Controls) often have a huge number of events that are never subscribed to. That's a whole lot of allocated storage not being used. There's an optimization built into the framework in the form of a EventHandlerList. This handy object stores event handlers only when they are actually used. All System.Windows.Forms.Control objects derive from System.ComponentModel.Component and it already provides an (protected) EventHandlerList that you can access in your derived Control.
To use it, you first create a static object that uniquely identifies your event, and then you provide the add {} and remove {} methods manually. Like so:
class MyUserControl : UserControl
{
private static readonly object FileOpenedKey = new Object();
public event EventHandler<FileOpenedEventArgs> FileOpened
{
add { Events.AddHandler(FileOpenedKey, value); }
remove { Events.RemoveHandler(FileOpenedKey, value); }
}
protected virtual void OnFileOpened(FileOpenedEventArgs e)
{
var handler = (EventHandler<FileOpenedEventArgs>)Events[FileOpenedKey];
if (handler != null)
handler(this, e);
}
}
Yes, you will need to create an event and subscribe to it. One suggestion following the standard pattern for events:
enum ControlStatus {Idle, LoadingFile, ...}
class StatusChangedEventArgs : EventArgs
{
public ControlStatus Status {get; private set;}
public StatusChangedEventArgs(ControlStatus status)
: base()
{
this.Status = status;
}
}
partial class MyControl : UserControl
{
public ControlStatus Status {get; private set;}
public event EventHandler<StatusChangedEventArgs> StatusChanged;
protected virtual void OnStatusChanged(StatusChangedEventArgs e)
{
var hand = StatusChanged;
if(hand != null) hand(this, e);
}
void LoadFiles()
{
...
Status = ControlStatus.LoadingFiles;
OnStatusChanged(new StatusChangedEventArgs(this.Status));
...
Status = ControlStatus.Idle;
OnStatusChanged(new StatusChangedEventArgs(this.Status));
}
}
partial class MyHostWindowsForm : Form
{
public MyHostWindowsForm()
{
var ctl = new MyControl();
...
ctl.StatusChanged += ctl_StatusChanged;
}
void ctl_StatusChanged(object sender, StatusChangedEventArgs e)
{
switch(e.Status)
{
case ControlStatus.Idle:
statusStripBar.Text = null;
break;
case ControlStatus.LoadingFiles:
statusStripBar.Text = "Loading file(s)";
break;
...
}
}
}
I've built a class that derives from System.Web.UI.WebControl. It basically renders pagination links (same as what you see on top of GridView when enabled) for use above a repeater.
I'm creating some anchor tags explicitly inside my nav control obviously, but they don't perform ajax postbacks. My understanding is that ajax requires POSTS to work right? Well, these would be GETs which I think is the problem.
Is there a way to achieve what I'm trying to do?
Thanks!
To take this advantage, you have to inherit the ICallbackEventHandler and implement its methods as follows.
public class CtlTest : WebControl, ICallbackEventHandler
{
private static readonly object EventClick = new object();
public CtlTest() : base(HtmlTextWriterTag.A) { }
public event EventHandler Click
{
add { base.Events.AddHandler(EventClick, value); }
remove { base.Events.RemoveHandler(EventClick, value); }
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Href, "javascript:" + this.Page.ClientScript.GetCallbackEventReference(this, null, "null", null));
}
protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
writer.Write("Submit Query");
}
protected virtual void OnClick(EventArgs e)
{
EventHandler handler = this.Events[EventClick] as EventHandler;
if (handler != null)
handler(this, e);
}
#region ICallbackEventHandler Members
string ICallbackEventHandler.GetCallbackResult()
{
return string.Empty;
}
void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
{
this.OnClick(EventArgs.Empty);
}
#endregion
}
Whereas you are working on a data pager control and it requires to update some portions of the page, it's better to write a non Ajax enabled control and put it and its relative controls within an UpdatePanel.
Ok, I figured it out. I simply made my class implement the IPostBackEventHandler. This makes your control fire an event when the user takes action on something. In my case, it's clicking a nav pagenumber: [1][2][3][4][5][Next >]
Then, inside my render where I create the Anchor tags, I add this to each href (pageStartRow is different for each):
PostBackOptions options = new PostBackOptions(this, pageStartRow.ToString());
writer.AddAttribute(HtmlTextWriterAttribute.Href, "javascript:" + Page.ClientScript.GetPostBackEventReference(options));
writer.RenderBeginTag(HtmlTextWriterTag.A);
The key is to pass something that uniquely identifies which link they clicked. This is done as the 2nd constructor parameter to the PostBackOptions class.
I then added the following items in my WebControl class:
// Defines the Click event.
public delegate void ClickHandler(object sender, GridPagingNavClickedEventArgs e);
public event ClickHandler Click;
//Invoke delegates registered with the Click event.
protected virtual void OnClick(GridPagingNavClickedEventArgs e)
{
if (Click != null)
{
Click(this, e);
}
}
public void RaisePostBackEvent(string eventArgument)
{
GridPagingNavClickedEventArgs e = new GridPagingNavClickedEventArgs(Convert.ToInt32(eventArgument));
OnClick(e);
}
The GridPagingNavClickedEventArgs contains a single item (pageNumber in my case).
Finally, in my aspx page (where I use the webcontrol), I do this in the Page_OnLoad:
gridNavTop.Click += new GridPagingNavigation.ClickHandler(gridNavTop_Click);
and this is the event code:
private void gridNavTop_Click(object sender, GridPagingNavClickedEventArgs e)
{
StartRow = e.PageStartRow;
}
As long as everything is inside an UpdatePanel, then it works perfectly!