I am wondering what the correct mechanism to enable communication between controls in WPF is. My goal is to not use conventional events and have to manually wire them up. The default behavior of routed commands (tunneling, bubbling) seems to be along the right lines but I guess I'm missing something.
Routed events are a new infrastructure provided by WPF which allows events to tunnel down the visual tree to the target element, or bubble up to the root element. When an event is raised, it “travels” up or down the visual tree invoking handlers for that event on any element subscribed to that event it encounters en route. Note that this tree traversal does not cover the entire visual tree, only the ancestral element
That is from this WPF Article
Using the image in the article, I want "Immediate Element #1" to initiate (raise) an event and then have "Immediate Element #2" handle that event. I'd like to achieve this without having to put any code in the "Root Element".
Basically fire an event (save, status updated, selection changed, etc..) from any where in my app, then have it be handled somewhere else with out the 2 parties knowing anything about each other. Is this possible?
I dont believe data bainding is the answer. I'd like to use Routed Events / Commands as they were designed just across the entire tree, not just within the source control's branch. Maybe it can't be done using routed events / commands, and data binding is the answer. I just dont know...
Any ideas?
The best mechanism is to refactor and separate the data view from the data model.
Create a data model that provides DependencyProperty properties (rather than standard C# properties) for each data point, but does not provide a UI. The values in the data model can affect each other when modified.
You can then bind each WPF element to the appropriate DependencyProperty from the data model. Modify the value in one element and all other elements are updated to reflect any data model changes in the bound properties.
If you want to transfer data between elements, Binding is the way to go. There are many tutorials and books about this on the net.
If you want to effect Style changes, then you can use DataTriggers, which also use Bindings.
There is no way to send events in the traditional sense between unrelated controls without wiring it up in the common root.
Related
I'm working on a area of an application that consists of the following parts:
Explorer - Contains a TreeView
PropertyInspector - Contains a PropertyGrid
Editor - Contains an Explorer and a PropertyInspector
As you can see, the parts are organized in a hierarchical fashion. My question is:
How do I make the selected item in the tree view used by the explorer the selected object of the property grid used by the property inspector?
When the tree view raises the SelectedItemChanged event a command is sent to ExplorerViewModel. The PropertyGrids SelectedObject is bound to a property on the PropertyInspectorViewModel. How do I connect the two view models? One approach could be to let EditorViewModel attach a method on the PropertyInspectorViewModel to an event raised by the ExplorerViewModel when the command is received but is this the way to go or can it be solved through data binding somehow?
The reason why the editor doesn't use a tree view and property grid directly is because the explorer and the property inspector, together with all the undo/redo functionality, validation code etc, are meant to be reused in other areas.
I would highly recommend using a MVVM framework. I had similar requirements and decided to adopt Tony Sneed's Simple MVVM Toolkit. It is very lightweight,very well documented and easy to understand, provides very useful capabilities and basically does must of the MVVM plumbing for you.
What you need in this case is make use of the Message Bus features:
"MessageBus
Sometimes you need to pass messages among view-models in your application. A good example would be when you want to navigate to a particular view based on some business logic. It would not be a good idea to reference the main view-model directly from another view-model. Doing so would create interdependencies between view-models that would be difficult to maintain (a phenomenon referred to as spaghetti code). This is where a message bus (also called an event mediator or aggregator) comes in handy. The CustomerViewModel, for example, can then send a message to the message bus using a specific token, and the MainPageViewModel can subscribe to receive a message whenever someone sends a message with this same token to the message bus. Message tokens are simply strings that can be defined as constants in a class." From the Programming Reference page
In addition to that I also used Josh Smith's RelayCommand to simplify ICommand notifications as #eran otzap suggested
The editor VM should coordinate the two child VMs.
Add a selected item property to explorer VM (if it's not there already) and make sure it fires property change notification.
The property inspector VM should have a similar property that is the item to show properties of.
The editor then watches the explorer VM's selected item property for changes, and when it changes passes the new item to the property inspector VM.
There's no need to involve a message bus here. Message buses are for ViewModels that are loosely coupled to pass messages to each other. For example application wide notification that a file has been opened.
If you used a message bus here then any reuse of the VMs would also trigger the message, which is not what you want.
Working on custom WPF control which is called "MultiSelectTreeView," which inherits from System.Windows.Controls.TreeView. Its purpose is to allow for multi-selecting and dragging and dropping.
The xaml for the view which contains this MultiSelectTreeView control binds an ObservableCollection that is exposed by the underlying view model.
A drag-and-drop operation might possibly involve many remove/add (or move) operations on the ObservableCollection, however I need to encapsulate all of the operations for a single drag-and-drop operation into a command object to support the undo/redoing of the drag-and-drop as a single atomic operation.
When I hook into the ViewModel.ObservableCollection's CollectionChanged event multiple events fire and from the perspective of the ViewModel, there is no way to know if any particular add/remove/move event will exist in isolation or whether it will be a part of a series of events, all related to a single user drag-and-drop.
I can imagine all sorts of wonky solutions, such as giving the MultiSelectTreeView control all sorts of in-depth knowledge of its underlying ViewModel's possible structure (to momentarily unhook the ObservableCollection's CollectionChanged event), but that doesn't feel right at all.
Perhaps I should create my own descendant of ObservableCollection, which supports a .MoveRange() method that only fires one event, or something along those lines.
I'm sure someone with more than just a few weeks of WPF experience could probably suggest a profoundly better solution than these.
I'm not sure if this will help you, but I just ran across it today.
Batch Updates with INotifyCollectionChanged
My MVVM app has a number of views that inherit from a base user control, which exposes an "ID" property. In the XAML this is bound to an ID property on the view's underlying view model, simply:
Id="{Binding Path=Id}"
The view model implements INotifyPropertyChanged, and its ID is set in the constructor. The ID is used to uniquely identify each view/view model, and is primarily used by a "desktop manager" to manage the user controls within the main window, rather like an MDI app. When my app starts I instantiate the various view models and their views, and assign the view models to the views' DataContext. I then pass the views to the desktop manager which places them on its canvas, positions them, etc.
The problem I have is that the view's ID is still null at this point, and only seems to get bound to the data context some time later (when the UI is rendered perhaps?). I have tried forcing the binding like this, but it doesn't help:-
var bindingExpression = widget.GetBindingExpression(DesktopElement.IdProperty);
bindingExpression.UpdateTarget();
It's not the end of the world, as I can pass the desktop manager my view and the ID from the view model, but it feels a little hacky. I was curious to know at what point in the control/window lifecycle the binding occurs, and whether there was some other way to force the binding to happen?
Thanks in advance
Andy
In order to understand how bindings are transferred, you need to understand the Dispatcher. Basically, it is a priority queue. Things like layout, bindings, rendering, input, etc. are placed in the queue at different priorities.
Now, from the sound of it, you never yield execution back to the Dispatcher. This means that Binding values can't transfer (when you manually call UpdateTarget you are just scheduling this on the Dispatcher). So, in short, you need to let the Dispatcher execute the queued operations before you finish initializing.
The easiest way to do this is to call BeginInvoke at a lower DispatcherPriority on a method to finish initialization. Because of the nature of how the layout system works, it can be tricky sometimes to pick the right priority, but you'll probably be okay if you go with DispatcherPriority.Loaded.
My suggestion would be to have an interface that your View Models implement, which exposes the Id property. You can then have your Desktop Manager grab the Id from the DataContext by casting it to the appropriate interface. This is likely a cleaner separation of responsibilities, as your Desktop Manager should probably know as little about the concrete View as possible (for the sake of testability.)
I've got two ListBox's with objects as an ItemsSource populating them. Right now, I'm using a DragDropHelper to let me drag an object from one ListBox to the 2nd ListBox. I run custom code to change an attribute on the Object and update my two ListBox collections of objects.
However, now I want to be able to drop one of these objects onto another control in the window. But, I dont want to necessarily "DROP" the object. I just want the external control to realize (by raising an event) that it just got dropped onto by an object with an ID.
To recap, I've got 2 listboxes. one listbox is Favorites, the other is NonFavorites. I can happily drag/drop between the two listboxes and everything works. now i want to drag a favorite/nonfavorite away from the listboxes and drop it onto another control. I want that control to simply say "HEY! I just got a favorite/nonfavorite object dropped on me".
any ideas?
I did something similar to this last year (.NET .3.5).
If I remember correctly when you "Drop" an object which has been selected and dragged (via the adorner layer) you are in essence holding a reference to the selected object. When that object is "Dropped" the "InstanceDroppedOnUserControlFoo_Handler(... args)" event handler has a untyped reference to the object that has been dropped.
From this you can cast (if the type is known) and access the Id field to your hearts content.
The question now is, does the drop target user control share the same ViewModel in it's DataContext as that of the Drag Source? As in most cases where this is not the case you will not get a reference in the event args, you will get null.
If this is the case you will need to explore these options for inter ViewModel communication:
Use a MVVM message passing framework (MVVM Light Framework see Messenger component)
or
Pub Sub composite events via the WPF Prism - EventAggregator:
Then follow this process (or something more tailored to your needs):
When an item has been selected and is being Dragged, hold its reference in a property of your Drag Source's ViewModel.
When the item is dropped, publish a message saying "I want the reference to the selected item which was being dragged".
The Drag Source can publish a message in response with the reference to the object which was dragged which will be received by the requesting ViewModel.
Obviously you can tailor the reference holding at this point to your needs. I will leave you with one last suggestion, it may be worth while considering the use of a controller class which manages this kind of operation. I have seen a controller being used by the Microsoft's Patterns & Practises in coordination with MVVM in the WPF CAG (PRISM) samples, so it is not unheard of.
I hope this makes sense.
I have created several WPF User Controls. The lowest level item is 'PostItNote.xaml'. Next, I have a 'NotesGroup.xaml' file that has an ItemsControl bound to a List of PostItNotes. Above that, I have a 'ProgrammerControl.xaml' file. Each ProgrammerControl has a grid with four different NotesGroup user controls on it (and each NotesGroup contains 0-many PostItNotes.
Then, I have my main window. It also has an ItemsControl, bound to a list of Programmers.
So, you end up with a high level visual view of a list of programmers, each programmer has four groups of tickets, each group of tickets has many PostItNotes.
The trouble I'm having, is that I want to respond to a mouse click event in my mainWindow's code behind file.
I can add a MouseClick event into my PostItNote.xaml.vb file and that is getting called when the user clicks a PostItNote, and I can re-raise the event; but I can't seem to get the NotesGroup to listen for that event. I'm not sure if that's even the correct approach.
When the user clicks the PostItNote, I'm going to do a bunch of business-logic type stuff that the PostItNote control doesn't have a reference to/doesn't know about it.
Can anyone point me in the right direction?
You have a couple choices:
Use the PreviewXXX events which are fired during the "tunneling" phase of WPF event routing. The parent controls can always preview the events going down through them to children.
Use the more advanced approach to hooking up events leveraging the AddHandler method to which you can pass a parameter called "handledEventsToo" which basically means you want to know when the event happened "within" you even if some descendent element handled the event itself.
I am going to take a flyer here. You probably don't want to be handling the event that high up; not really anyway. You are catching the event at the lower levels, which is unavoidable. Consider invoking a routed command from the PostItNote click event handler.
The routed commands bubble up and tunnel down through the tree. You can have an architecture where a high-level handler can listen to a logical event (Opening a postit note perhaps?). The handler for this doesn't need to care where the command originates from. It might be from you clicking something, it might be from clicking on a toolbar button. Both are valid scenarios.
It sounds like you are creating some kind of custom UI, am I right? You want the application to respond to the users interactions. That is what the RoutedCommands are for.