Call RelayCommand in another class MVVM - c#

I'm using a RelayCommand with delegate access in my project. it's a call with a behavior. When I used Directly in ViewModel it works fine.
but I would like to pass this command in a class in order to use it generically. the behavior still works but no sign of life of the command.
The command in my ViewModel(Work):
LoadCommand = new RelayCommand<object>( new Action<object>(
obj =>
{
if (Busy)
return;
Busy = true;
System.Threading.ThreadPool.QueueUserWorkItem(
delegate
{
Application.Current.Dispatcher.BeginInvoke(new Action(
delegate
{
// myReference.AddDatas(Mvts);
AddMoreItems();
Busy = false;
}));
});
}));
And the command in my new class (doesn't work):
public Action<object> LoadCommand(Ref<ObservableCollection<T>> myList)
{
return new Action<object>(
obj =>
{
if (Busy)
return;
Busy = true;
System.Threading.ThreadPool.QueueUserWorkItem(
delegate
{
Application.Current.Dispatcher.Invoke(new Action(
delegate
{
AddDatas(myList);
Busy = false;
}));
});
}));
Call with
LoadCommand = new RelayCommand<object>(myReference.LoadCommand(Mvts));
All I know is that after putting breakpoints at the beginning of the order, it is not called.
For more informations my behavior call LoadCommand when scroll of datagrid is bottom
public class ScrollViewerMonitor
{
public static DependencyProperty AtEndCommandProperty
= DependencyProperty.RegisterAttached(
"AtEndCommand", typeof(ICommand),
typeof(ScrollViewerMonitor),
new PropertyMetadata(OnAtEndCommandChanged));
public static ICommand GetAtEndCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(AtEndCommandProperty);
}
public static void SetAtEndCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(AtEndCommandProperty, value);
}
public static void OnAtEndCommandChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
if (element != null)
{
element.Loaded -= element_Loaded;
element.Loaded += element_Loaded;
}
}
static void element_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.Loaded -= element_Loaded;
ScrollViewer scrollViewer = FindChildOfType<ScrollViewer>(element);
if (scrollViewer == null)
{
return;
}
var dpd = DependencyPropertyDescriptor.FromProperty(ScrollViewer.VerticalOffsetProperty, typeof(ScrollViewer));
dpd.AddValueChanged(scrollViewer, delegate (object o, EventArgs args)
{
bool atBottom = scrollViewer.VerticalOffset
>= scrollViewer.ScrollableHeight;
if (atBottom)
{
var atEnd = GetAtEndCommand(element);
if (atEnd != null)
{
atEnd.Execute(null);
}
}
});
}
static T FindChildOfType<T>(DependencyObject root) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--)
{
var child = VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
return typedChild;
}
queue.Enqueue(child);
}
}
return null;
}
}
although it reached in both cases atEnd.Execute(null);
But when command execute in other file the property below IsAlive = False
and when command is directly execute in VM IsAlive is to True with my target object

There is no chance to call Command, cause you are calling not the same instance of your viewModel.
To make some actions(for example, run your Command or edit some property) in your viewModel from another viewModel, it is better to use EventAggregator pattern.
In my view, the best approach is using EventAggregator pattern of Prism framework. The Prism simplifies MVVM pattern. However, if you have not used Prism, you can use Rachel Lim's tutorial - simplified version of EventAggregator pattern by Rachel Lim. I highly recommend you Rachel Lim's approach.
If you use Rachel Lim's tutorial, then you should create a common class:
public static class EventSystem
{...Here Publish and Subscribe methods to event...}
And publish an event into your OptionViewModel:
eventAggregator.GetEvent<ChangeStockEvent>().Publish(
new TickerSymbolSelectedMessage{ StockSymbol = “STOCK0” });
then you subscribe in constructor of another your MainViewModel to an event:
eventAggregator.GetEvent<ChangeStockEvent>().Subscribe(ShowNews);
public void ShowNews(TickerSymbolSelectedMessage msg)
{
// Handle Event
}
The Rachel Lim's simplified approach is the best approach that I've ever seen. However, if you want to create a big application, then you should read this article by Magnus Montin and at CSharpcorner with an example.

Those two are completely different, the first one is some kind of property which shows a proper Command binding and second one is a method returning RelayCommand. I dont think you can bind a Command property to a method. That might be reason why it doesnt work.

Related

Bind command in view model to keyboard shortcut

I'm using C#, WPF, ReactiveUI and Prism to create an application with many different views (user controls). On some views there are buttons/menu items that bind to a command in the view model. I would like these buttons to also activate using a key combination such as ctrl+s, etc....
What I've tried
InputBindings but that only works when the view that defines these input bindings has focus.
ApplicationCommands the predefined commands like ApplicationCommands.Close seem useful. I can reference them both in the view and the view model, but I don't know how subscribe to them in my view model. It also seems that I have to 'activate' the command first, or at least change CanExecute since any button bound to such command stays disabled.
What I wish for
Let's say I have a view that represents the top menu bar MenuView with a button myButton and a corresponding view model MenuViewModel with a command myCommand. I would like to bind myButton to myCommand and the keyboard shortcut ctrl+u to myCommand without MenuView knowing about the implementation of its view model. The keyboard shortcut should work as long as the window that contains MenuView has focus.
I don't really care if the keyboard short-cut is either in the view or view model.
You could create an attached Blend behaviour that handles the PreviewKeyDown event of the parent window:
public class KeyboardShortcutBehavior : Behavior<FrameworkElement>
{
private Window _parentWindow;
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(nameof(Command), typeof(ICommand),
typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(null));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty ModifierKeyProperty =
DependencyProperty.Register(nameof(ModifierKey), typeof(ModifierKeys),
typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(ModifierKeys.None));
public ModifierKeys ModifierKey
{
get { return (ModifierKeys)GetValue(ModifierKeyProperty); }
set { SetValue(ModifierKeyProperty, value); }
}
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register(nameof(Key), typeof(Key),
typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(Key.None));
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
AssociatedObject.Unloaded += AssociatedObject_Unloaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
_parentWindow = Window.GetWindow(AssociatedObject);
if(_parentWindow != null)
{
_parentWindow.PreviewKeyDown += ParentWindow_PreviewKeyDown;
}
}
private void ParentWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(Command != null && ModifierKey != ModifierKeys.None && Key != Key.None && Keyboard.Modifiers == ModifierKey && e.Key == Key)
Command.Execute(null);
}
private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)
{
if(_parentWindow != null)
{
_parentWindow.PreviewKeyDown -= ParentWindow_PreviewKeyDown;
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObject_Loaded;
AssociatedObject.Unloaded -= AssociatedObject_Loaded;
}
}
Sample usage:
<TextBox xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<i:Interaction.Behaviors>
<local:KeyboardShortcutBehavior ModifierKey="Ctrl" Key="U" Command="{Binding myCommand}" />
</i:Interaction.Behaviors>
</TextBox>
In code behind easy. Create some utility function that eventually lead to an observable of the parent window key events. Note that you will need the ReactiveUI.Events library.
Some utils for handling load and unload of controls.
public static void LoadUnloadHandler
( this FrameworkElement control
, Func<IDisposable> action
)
{
var state = false;
var cleanup = new SerialDisposable();
Observable.Merge
(Observable.Return(control.IsLoaded)
, control.Events().Loaded.Select(x => true)
, control.Events().Unloaded.Select(x => false)
)
.Subscribe(isLoadEvent =>
{
if (!state)
{
// unloaded state
if (isLoadEvent)
{
state = true;
cleanup.Disposable = new CompositeDisposable(action());
}
}
else
{
// loaded state
if (!isLoadEvent)
{
state = false;
cleanup.Disposable = Disposable.Empty;
}
}
});
}
public static IObservable<T> LoadUnloadHandler<T>(this FrameworkElement control, Func<IObservable<T>> generator)
{
Subject<T> subject = new Subject<T>();
control.LoadUnloadHandler(() => generator().Subscribe(v => subject.OnNext(v)));
return subject;
}
and one specifically for handling the window of a loaded control
public static IObservable<T> LoadUnloadHandler<T>
(this FrameworkElement control, Func<Window, IObservable<T>> generator)
{
Subject<T> subject = new Subject<T>();
control.LoadUnloadHandler(() => generator(Window.GetWindow(control)).Subscribe(v => subject.OnNext(v)));
return subject;
}
and finally a key handler for the parent window of any control
public static IObservable<KeyEventArgs> ParentWindowKeyEventObservable(this FrameworkElement control)
=> control.LoadUnloadHandler((Window window) => window.Events().PreviewKeyDown);
now you can do
Button b;
b.ParentWindowKeyEventObservable()
.Subscribe( kEvent => {
myCommand.Execute();
}
It might seem a bit complex but I use the LoadUnloadHandler on most user controls to aquire and dispose resources as the UI lifecycle progresses.
You want to use KeyBindings for this. This allows you to bind keyboard key combos to a command. Read the docs here: https://msdn.microsoft.com/en-us/library/system.windows.input.keybinding(v=vs.110).aspx

ObservableCollection Collection Changed event not firing

I have an observable collection in my ViewModel, bound to a datagrid. I want to implement some logic for refreshing the data in other windows based on changes to the collection/ updates to the database (using LINQ to SQL).
Here is my view model code:
public FTViewModel(int JobID)
{
_windowCloseAction = new DelegateCommand(OnWindowClose);
_oFTrn = new ObservableFilesTransmitted(_dataDc, JobID);
_oFTrn.CollectionChanged += oFTrnCollectionChanged;
}
void oFTrnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (FilesTransmitted f in e.NewItems)
f.PropertyChanged += FilesTransmitted_PropertyChanged;
}
if (e.OldItems != null)
{
foreach (FilesTransmitted f in e.OldItems)
f.PropertyChanged -= FilesTransmitted_PropertyChanged;
}
}
void FilesTransmitted_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "DocumentNumber")
{
_filesTransmittedChange = true;
}
_refreshViews = true;
}
and the ObservableCollection constructor:
class ObservableFilesTransmitted : ViewableCollection<FilesTransmitted>
{
public ObservableFilesTransmitted(DocControlDC dataDc, int ID)
{
foreach (FilesTransmitted ftran in dataDc.FilesTransmitteds.Where(x=>x.JobID==ID).OrderByDescending(x => x.TransmittalName))
{
this.Add(ftran);
}
}
}
The debugger does not stop in the oFTrnCollectionChanged. I think because the call to create the observable collection happens before I add the CollectionChanged event. But obvously I can't switch those two lines. I've looked at various StackOverflow and CodeProject topics on this, and it seems like what I have should work. Do I need to add and remove a dummy item just to get the CollectionChanged hander called? What am I missing?
It seems like perhaps I should have a constructor (for the observable collection) that does not add any members, and a function that adds the members from the database. Then I can call new, add the collectionchanged handler, and then fill the collection. I am hoping to avoid that level of rewrite though, but perhaps it's the only reasonable way.
When I run in to this the easiest way to solve it is just subscribe manually at the start.
public FTViewModel(int JobID)
{
_windowCloseAction = new DelegateCommand(OnWindowClose);
_oFTrn = new ObservableFilesTransmitted(_dataDc, JobID);
foreach(var item in _oFTrn)
{
item.PropertyChanged += FilesTransmitted_PropertyChanged;
}
_oFTrn.CollectionChanged += oFTrnCollectionChanged;
}
However a even better solution is instead of using a class derived from ObserveableCollection<T> use a class derived from BindingList<T>. Any member raising their PropertyChanged event will cause the collection to raise ListChanged with the change type of ItemChanged
public FTViewModel(int JobID)
{
_windowCloseAction = new DelegateCommand(OnWindowClose);
_oFTrn = new ObservableFilesTransmitted(_dataDc, JobID);
_oFTrn.CollectionChanged += oFTrnListChanged;
}
void oFTrnListChanged(object sender, ListChangedEventArgs e)
{
if (e.ListChangedType == ListChangedType.ItemChanged)
{
if (e.PropertyDescriptor.Name == "DocumentNumber")
{
_filesTransmittedChange = true;
}
}
_refreshViews = true;
}
I have simply changed the ObservableCollection constructor and added a populate function:
New view model code:
public FTViewModel(int JobID)
{
_oFTrn = new ObservableFilesTransmitted(_dataDc, JobID);
_oFTrn.CollectionChanged += oFTrnCollectionChanged;
_oFTrn.FillCollection();
}
new ObservableCollection class:
class ObservableFilesTransmitted : ViewableCollection<FilesTransmitted>
{
DocControlDC _dc = null;
int _jobID = 0;
public ObservableFilesTransmitted(DocControlDC dataDc, int ID)
{
_dc = dataDc;
_jobID = ID;
}
public void FillCollection()
{
foreach (FilesTransmitted ftran in _dc.FilesTransmitteds.Where(x=>x.JobID==_jobID).OrderByDescending(x => x.TransmittalName))
{
this.Add(ftran);
}
}
}
And it all works as expected. But it gets called for each item added. I may play with the idea that I simply loop through the collection and add the propertychanged handler for each item in the viewmodel constructor. Seems like less of a performance hit that way.

Return a value from a command?

I am trying to implement a MVVM architecture in my WPF application, and I want to be able to modify the model after executing a command. Note that I am not using any kind of MVVM framework.
I've got a base Command class as follows:
namespace MyApplication.Commands {
public abstract class CommandBase : ICommand {
protected static BackgroundWorker Worker = new BackgroundWorker();
protected static string _result;
public string Result {
get {
return _result;
}
set {
_result = value;
}
}
public abstract void DoWork(DoWorkEventArgs args);
public virtual void Execute(object parameter) {
Worker = new BackgroundWorker();
Worker.DoWork += (o, args) => DoWork(args);
Worker.RunWorkerCompleted += (sender, args) => {
RaiseCanExecuteChanged(EventArgs.Empty);
};
Worker.ProgressChanged += (sender, args) => {
RaiseCanExecuteChanged(EventArgs.Empty);
};
Worker.WorkerReportsProgress = true;
Worker.RunWorkerAsync(parameter);
}
public virtual bool CanExecute(object parameter) {
return !Worker.IsBusy;
}
public event EventHandler CanExecuteChanged;
protected virtual void RaiseCanExecuteChanged(EventArgs e) {
CanExecuteChanged(this, e);
}
}
}
I've got my actual command which implements this, as follows:
namespace MyApplication.Commands {
internal class DoSomethingCommand : CommandBase {
public override void DoWork(DoWorkEventArgs args) {
Worker.ReportProgress(0);
var success = false;
try {
var parameter = args.Argument as int?;
success = DoSomething(parameter);
} finally {
args.Result = success;
}
}
private static bool DoSomething(int parameter) {
// Do something expensive here...
System.Func func = (arg) => {
Thread.Sleep(arg);
return true;
};
// etc etc...
var success = func.Invoke(parameter);
return success;
}
}
}
I'm using this as a way to perform expensive operations while still maintaining the MVVM model. I had to perform RaiseCanExecuteChanged in the progress and completion events, otherwise the UI will not update the corresponding button state.
However, now I want to modify a property of the current Model being accessed (AKA selected in a ListView control), which is exposed as a property of the ViewModel. How can I do this while still maintaining the MVVM architecture?
Also is there any better way of passing the current state of the ViewModel to my commands? Currently, I'm using a MultiBinding plus an IMultiValueConverter that just allows passing an object[].
Why not passing a reference to your viewModel in the constructor of your command and and hold it locally in a field. That way you should be able to manipulate the whole viewModel using its exposed methods and properties.
Regarding your second question:
Also is there any better way of passing the current state of the ViewModel to my commands?
Your command should be implemented in the viewModel so you shouldn't have to provide the viewModel's state. If that's not the case why not just pass in the whole viewModel by passing DataContext in the CommandParameter property of the control.

A design pattern to disable event handling

To simply illustrate my dilemma, let say that I have the following code:
class A
{
// May be set by a code or by an user.
public string Property
{
set { PropertyChanged(this, EventArgs.Empty); }
}
public EventHandler PropertyChanged;
}
class B
{
private A _a;
public B(A a)
{
_a = a;
_a.PropertyChanged += Handler;
}
void Handler(object s, EventArgs e)
{
// Who changed the Property?
}
public void MakeProblem()
{
_a.Property = "make a problem";
}
}
In order to perform its duty, class B have to react on A's PropertyChanged event but also is capable of alternating that property by itself in certain circumstances. Unfortunately, also other objects can interact with the Property.
I need a solution for a sequential flow. Maybe I could just use a variable in order to disable an action:
bool _dontDoThis;
void Handler(object s, EventArgs e)
{
if (_dontDoThis)
return;
// Do this!
}
public void MakeProblem()
{
_dontDoThis = true;
_a.Property = "make a problem";
_dontDoThis = false;
}
Are there a better approaches?
Additional considerations
We are unable to change A.
A is sealed.
There are also other parties connected to the PropertyChanged event and I don't know who their are. But when I update the Property from B, they shouldn't be also notified. But I'm unable to disconnect them from the event because I don't know them.
What if also more threads can interact with the Property in the mean time?
The more bullets solved, the better.
Original problem
My original problem is a TextBox (WPF) that I want to complement depending on its content and focus. So I need to react on TextChanged event and I also need to omit that event if its origin is derived from my complements. In some cases, other listeners of a TextChanged event shouldn't be notified. Some strings in certain state and style are invisible to others.
If it is so important not to handle events you initiated, maybe you should change the way you set Property to include the initiator of the change?
public class MyEventArgs : EventArgs
{
public object Changer;
}
public void SetProperty(string p_newValue, object p_changer)
{
MyEventArgs eventArgs = new MyEventArgs { Changer = p_changer };
PropertyChanged(this, eventArgs);
}
And then in your handler - simply check your are not the initiator.
I find all these changes in registration and members very problematic in terms on multi threading and extensibility.
Well essentially you are trying to break the event delegation mechanism and any "solution" to that is going to be brittle since updates to the BCL might break your code. You could set the backing field using reflection. This of course would require that you do have permissions to do this and seeing the generic framing of the question it might not always be that you have the needed permissions
public void MakeProblem()
{
if (_backingField == null) {
_backingField = = _a.GetType().GetField(fieldname)
}
_backingField.SetValue(_a,"make a problem");
}
but as I started out, you are trying to break the event delegation mechanism. The idea is that the receivers of the event are independent. Disabling might lead to so very hard to find bugs because looking at any given piece of code it looks correct but only when you realize that some devious developer has hack the delegation mechanism do you realize why the information that is shown on screen seems to be a cached version of the actual value. The debugger shows the expected value of the property but because the event was hidden the handler responsible for updating the display was never fired and hence an old version is displayed (or the log shows incorrect information so when you are trying to recreate a problem a user has reported based on the content of the log you will not be able to because the information in the log is incorrect because it was based on no one hacking the event delegation mechanism
To my opinion your solution is possible, though I would have created a nested IDisposable class inside B that does the same thing with 'using', or put the '_dontDoThis = false' inside a 'finally' clause.
class A
{
// May be set by a code or by an user.
public string Property
{
set { if (!_dontDoThis) PropertyChanged(this, EventArgs.Empty); }
}
public EventHandler PropertyChanged;
bool _dontDoThis;
}
class B
{
private class ACallWrapper : IDisposable
{
private B _parent;
public ACallWrapper(B parent)
{
_parent = parent;
_parent._a._dontDoThis = true;
}
public void Dispose()
{
_parent._a._dontDoThis = false;
}
}
private A _a;
public B(A a)
{
_a = a;
_a.PropertyChanged += Handler;
}
void Handler(object s, EventArgs e)
{
// Who changed the Property?
}
public void MakeProblem()
{
using (new ACallWrapper(this))
_a.Property = "make a problem";
}
}
On the other hand, I would've used the 'internal' modifier for these things if those two classes are inside the same assembly.
internal bool _dontDoThis;
That way, you keep a better OOP design.
Moreover, if both classes are on the same assembly, I would've written the following code inside A:
// May be set by a code or by an user.
public string Property
{
set
{
internalSetProperty(value);
PropertyChanged(this, EventArgs.Empty);
}
}
internal internalSetProperty(string value)
{
// Code of set.
}
In this case, B could access internalSetProperty without triggering to PropertyChanged event.
Thread Sync:
NOTE: The next section applies to WinForms - I don't know if it applies to WPF as well.
For thread synchronizations, because we're referring to a control. you could use the GUI thread mechanism for synchronization:
class A : Control
{
public string Property
{
set
{
if (this.InvokeRequired)
{
this.Invoke((Action<string>)setProperty, value);
reutrn;
}
setProperty(value);
}
}
private void setProperty string()
{
PropertyChanged(this, EventArgs.Empty);
}
}
Great question.
As a general case, you can not mess around with event handlers of sealed classes. Normally you could override A's hypothetical OnPropertyChanged and based on some flag either raise the event or not. Alternatively you could provide a setter method that does not raise event, as suggested by #Vadim. However, if A is sealed your best option is to add flag to a lister, just as you did. That will enable you to recognize PropertyChanged event raised by B, but you won't be able to suppress the event for other listeners.
Now, since you provided context... There is a way of doing exactly this in WPF. All that needs to be done is B's handler for TextBox.TextChanged needs to set e.Handled = _dontDoThis. That will supress notifications for all other listeners, provided B's one was added as the first one. How to make sure this happens? Reflection!
UIElement exposes only AddHandler and RemoveHandler methods, there is no InsertHandler that would allow to manually specifiy the priority for the handler. However, a quick peek into .NET source code (either download the whole thing or query what you need) reveals that AddHandler forwards arguments to an interal method EventHandlersStore.AddRoutedEventHandler, which does this:
// Create a new RoutedEventHandler
RoutedEventHandlerInfo routedEventHandlerInfo =
new RoutedEventHandlerInfo(handler, handledEventsToo);
// Get the entry corresponding to the given RoutedEvent
FrugalObjectList<RoutedEventHandlerInfo> handlers = (FrugalObjectList<RoutedEventHandlerInfo>)this[routedEvent];
if (handlers == null)
{
_entries[routedEvent.GlobalIndex] = handlers = new FrugalObjectList<RoutedEventHandlerInfo>(1);
}
// Add the RoutedEventHandlerInfo to the list
handlers.Add(routedEventHandlerInfo);
All this stuff is internal, but can be recreated using reflection:
public static class UIElementExtensions
{
public static void InsertEventHandler(this UIElement element, int index, RoutedEvent routedEvent, Delegate handler)
{
// get EventHandlerStore
var prop = typeof(UIElement).GetProperty("EventHandlersStore", BindingFlags.NonPublic | BindingFlags.Instance);
var eventHandlerStoreType = prop.PropertyType;
var eventHandlerStore = prop.GetValue(element, new object[0]);
// get indexing operator
PropertyInfo indexingProperty = eventHandlerStoreType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(x => x.Name == "Item" && x.GetIndexParameters().Length == 1 && x.GetIndexParameters()[0].ParameterType == typeof(RoutedEvent));
object handlers = indexingProperty.GetValue(eventHandlerStore, new object[] { routedEvent });
if (handlers == null)
{
// just add the handler as there are none at the moment so it is going to be the first one
if (index != 0)
{
throw new ArgumentOutOfRangeException("index");
}
element.AddHandler(routedEvent, handler);
}
else
{
// create routed event handler info
var constructor = typeof(RoutedEventHandlerInfo).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single();
var handlerInfo = constructor.Invoke(new object[] { handler, false });
var insertMethod = handlers.GetType().GetMethod("Insert");
insertMethod.Invoke(handlers, new object[] { index, handlerInfo });
}
}
}
Now calling InsertEventHandler(0, textBox, TextBox.TextChangedEvent, new TextChangedEventHandler(textBox_TextChanged)) will make sure your handler will be the first one on the list, enabling you to suppress notifications for other listeners!
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var textBox = new TextBox();
textBox.TextChanged += (o, e) => Console.WriteLine("External handler");
var b = new B(textBox);
textBox.Text = "foo";
b.MakeProblem();
}
}
class B
{
private TextBox _a;
bool _dontDoThis;
public B(TextBox a)
{
_a = a;
a.InsertEventHandler(0, TextBox.TextChangedEvent, new TextChangedEventHandler(Handler));
}
void Handler(object sender, TextChangedEventArgs e)
{
Console.WriteLine("B.Handler");
e.Handled = _dontDoThis;
if (_dontDoThis)
{
e.Handled = true;
return;
}
// do this!
}
public void MakeProblem()
{
try
{
_dontDoThis = true;
_a.Text = "make a problem";
}
finally
{
_dontDoThis = false;
}
}
}
Output:
B.Handler
External handler
B.Handler
I found one solution with regard to third parties, that are connected to the property and we don't want to nofify them when that property changed.
There are though the requirements:
We are capable of override the A.
The A has a virtual method that is invoked when property changed and allows to suspend the event to be raised.
The event is raised immediately when property is being changed.
The solution is to replace the A by MyA, as follows:
class A
{
// May be set by a code or by an user.
public string Property
{
set { OnPropertyChanged(EventArgs.Empty); }
}
// This is required
protected virtual void OnPropertyChanged(EventArgs e)
{
PropertyChanged(this, e);
}
public EventHandler PropertyChanged;
}
// Inject MyA instead of A
class MyA : A
{
private bool _dontDoThis;
public string MyProperty
{
set
{
try
{
_dontDoThis = true;
Property = value;
}
finally
{
_dontDoThis = false;
}
}
}
protected override void OnPropertyChanged(EventArgs e)
{
// Also third parties will be not notified
if (_dontDoThis)
return;
base.OnPropertyChanged(e);
}
}
class B
{
private MyA _a;
public B(MyA a)
{
_a = a;
_a.PropertyChanged += Handler;
}
void Handler(object s, EventArgs e)
{
// Now we know, that the event is not raised by us.
}
public void MakeProblem()
{
_a.MyProperty = "no problem";
}
}
Unfortunately we still use back bool field and we assume a single thread. To rid of the first, we could use a refactored solution suggest by EZSlaver (here). First, create a disposable wrapper:
class Scope
{
public bool IsLocked { get; set; }
public static implicit operator bool(Scope scope)
{
return scope.IsLocked;
}
}
class ScopeGuard : IDisposable
{
private Scope _scope;
public ScopeGuard(Scope scope)
{
_scope = scope;
_scope.IsLocked = true;
}
public void Dispose()
{
_scope.IsLocked = false;
}
}
Then the MyProperty might be refactored to:
private readonly Scope _dontDoThisScope = new Scope();
public string MyProperty
{
set
{
using (new ScopeGuard (_dontDoThisScope))
Property = value;
}
}

Whilst using drag and drop, can I cause a Treeview to Expand the node over which the user hovers?

In brief:
Is there any built-in function in .Net 2.0 to Expand TreeNodes when hovered over whilst a drag and drop operation is in progress?
I'm using C# in Visual Studio 2005.
In more detail:
I've populated a Treeview control with a multi levelled, multinoded tree (think organisational chart or file/folder dialog) and I want to use drag and drop to move nodes within the tree.
The drag drop code works well, and I can drop onto any visible node, however I would like my control to behave like Windows Explorer does when dragging files over the folder pane. Specifically, I'd like each folder to open if hovered over for 1/2 second or so.
I've begun developing a solution using Threading and a Sleep method but I'm running into problems and wondered if there was something in place already, if not I will knuckle down and learn how to use threading (it's about time, but I was hoping to get this app out quickly)
Do I need to write my own code to handle Expanding a TreeNode when hovered over in drag-drop mode?
You can use the DragOver event; it fires repeatedly while you are dragging an object
Opening after a delay can be done very easily with two extra variables that note the last object under the mouse and the time. No threading or other tricks required (lastDragDestination and lastDragDestinationTime in my example)
From my own code:
TreeNode lastDragDestination = null;
DateTime lastDragDestinationTime;
private void tvManager_DragOver(object sender, DragEventArgs e)
{
IconObject dragDropObject = null;
TreeNode dragDropNode = null;
//always disallow by default
e.Effect = DragDropEffects.None;
//make sure we have data to transfer
if (e.Data.GetDataPresent(typeof(TreeNode)))
{
dragDropNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
dragDropObject = (IconObject)dragDropNode.Tag;
}
else if (e.Data.GetDataPresent(typeof(ListViewItem)))
{
ListViewItem temp (ListViewItem)e.Data.GetData(typeof(ListViewItem));
dragDropObject = (IconObject)temp.Tag;
}
if (dragDropObject != null)
{
TreeNode destinationNode = null;
//get current location
Point pt = new Point(e.X, e.Y);
pt = tvManager.PointToClient(pt);
destinationNode = tvManager.GetNodeAt(pt);
if (destinationNode == null)
{
return;
}
//if we are on a new object, reset our timer
//otherwise check to see if enough time has passed and expand the destination node
if (destinationNode != lastDragDestination)
{
lastDragDestination = destinationNode;
lastDragDestinationTime = DateTime.Now;
}
else
{
TimeSpan hoverTime = DateTime.Now.Subtract(lastDragDestinationTime);
if (hoverTime.TotalSeconds > 2)
{
destinationNode.Expand();
}
}
}
}
EDIT
I have a new solution, a bit far-fetched, but it works... It uses a DelayedAction class to handle delayed execution of an action on the main thread :
DelayedAction<T>
public class DelayedAction<T>
{
private SynchronizationContext _syncContext;
private Action<T> _action;
private int _delay;
private Thread _thread;
public DelayedAction(Action<T> action)
: this(action, 0)
{
}
public DelayedAction(Action<T> action, int delay)
{
_action = action;
_delay = delay;
_syncContext = SynchronizationContext.Current;
}
public void RunAfterDelay()
{
RunAfterDelay(_delay, default(T));
}
public void RunAfterDelay(T param)
{
RunAfterDelay(_delay, param);
}
public void RunAfterDelay(int delay)
{
RunAfterDelay(delay, default(T));
}
public void RunAfterDelay(int delay, T param)
{
Cancel();
InitThread(delay, param);
_thread.Start();
}
public void Cancel()
{
if (_thread != null && _thread.IsAlive)
{
_thread.Abort();
}
_thread = null;
}
private void InitThread(int delay, T param)
{
ThreadStart ts =
() =>
{
Thread.Sleep(delay);
_syncContext.Send(
(state) =>
{
_action((T)state);
},
param);
};
_thread = new Thread(ts);
}
}
AutoExpandTreeView
public class AutoExpandTreeView : TreeView
{
DelayedAction<TreeNode> _expandNode;
public AutoExpandTreeView()
{
_expandNode = new DelayedAction<TreeNode>((node) => node.Expand(), 500);
}
private TreeNode _prevNode;
protected override void OnDragOver(DragEventArgs e)
{
Point clientPos = PointToClient(new Point(e.X, e.Y));
TreeViewHitTestInfo hti = HitTest(clientPos);
if (hti.Node != null && hti.Node != _prevNode)
{
_prevNode = hti.Node;
_expandNode.RunAfterDelay(hti.Node);
}
base.OnDragOver(e);
}
}

Categories