I need to click on an TreeViewItem and open an dialog window with the data of that TreeViewItem, later based on that data I will run another command.
My actual problem is: I can't click on it because treeviewitem doesn't have the command property.
My scenario: I have 2 Models with 2 properties that will be used to create my TreeViewItems. On my ViewModel I create them, and organize them inside each other based on their properties and then store them inside One Collection.
Here's my xaml to bind the elements:
<TreeView ItemsSource="{Binding Local}">
<TreeView.DataContext>
<data:ItemViewModel/>
</TreeView.DataContext>
</Treeview>
//In my "Local" property I have 3 TreeViewItems with other items inside them which I want to execute the commands
I couldn't find a way to create a datatemplate for that specific scenario. Even tried to create a datatemplate with a Hyperlink (thought it would be a temporary solution) inside it, but would not execute any command.
MVVM pattern is to use one of the many "Event to Command" implementations out there. You basically bind the "Event to Command" object to the Click event and then a command in your VM gets bound to the "Event to Command" object and it gets mapped behind the scenes for you and handles all the enabled / disabled stuff.
You can see an example of one of the implementations here:
WPF Binding UI events to commands in ViewModel
You should be binding to a collection whose objects have a collection as a public property and templating by type into whatever you want to see in each treeviewitem.
Like this sample:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.hierarchicaldatatemplate?view=netframework-4.7.2
Technically, you could have a button whose template was a textblock or something and that would then have the behaviour of a button such as click and command.
But I'd be more likely to use an inputbinding.
Here's an example:
<DataTemplate DataType="{x:Type local:LineTerrainVM}">
<Grid Background="Transparent">
<Grid.InputBindings>
<MouseBinding MouseAction="RightClick" Command="{Binding FixLineCommand}"/>
</Grid.InputBindings>
You can give that a commandparameter="{Binding .}" and it'll pass the viewmodel as a parameter.
You could also use relativesource to the datacontext of the treeview to get at a parent viewmodel and define a command in that to do your stuff.
Since that stuff you want to do is a view responsibility you could rely on routed events without "breaking" mvvm. A click in any treeviewitem could be handled at the treeview level and use the originalsource to get to the treeviewitem clicked. Then grab it's datacontext for the viewmodel of whatever that is.
Rough idea:
<TreeView Name="tv" ItemsSource="{Binding Families}" FrameworkElement.PreviewMouseLeftButtonDown="Tv_PreviewMouseLeftButtonDown"
And the handler:
private void Tv_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var vm = ((FrameworkElement)e.OriginalSource).DataContext;
// do something with viewmodel
}
You could then do something like new up your dialog window, set it's datacontext to that viewmodel you just got and showdialog it.
Related
I'm adding a close button to my tabs using the following guide:
http://www.codeproject.com/Articles/84213/How-to-add-a-Close-button-to-a-WPF-TabItem
This has become a problem because the event uses the 'parent' of the added tab to remove that tab from the tabcontrol. I'm binding the tab control using mvvm, so the parent property is apparently not being set and giving me a null reference exception for the parent when the event tries to remove from it.
Here's the binding so you get the idea:
<TabControl Name="tabControl" Margin="0,22,0.2,-5.2" ItemsSource="{Binding Tabs}" Background="#FF4C76B2"/>
Heres where the tabs are being added.
private void AddTab(object tabName)
{
ClosableTab newTab = new ClosableTab();
newTab.Title = "title?";
//newTab.Header = tabName;
TextBox test = new TextBox();
test.Text = "CONTENT (" + tabName + ") GOES HERE";
newTab.Content = test;
Tabs.Add(newTab);
OnPropertyChanged("Tabs");
}
Here is the event where the null reference is taking place:
void button_close_Click(object sender, RoutedEventArgs e)
{
((TabControl)this.Parent).Items.Remove(this);
}
As I see it there are two options:
try to find another way to remove the tab (without the parent
property)
try to find a way to somehow set the parent property (which cant be
done directly, it throws a compiler error)
That doesn't sound like MVVM to me. We work with data, not UI elements. We work with collections of classes that contain all of the properties required to fulfil some requirement and data bind those properties to the UI controls in DataTemplates. In this way, we add UI controls by adding data items into these collections and let the wonderful WPF templating system take care of the UI.
For example, you have a TabControl that we want to add or remove TabItems from... in a proper MVVM way. First, we need a collection of items that can represent each TabItem:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<string>), typeof(TestView));
public ObservableCollection<string> Items
{
get { return (ObservableCollection<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
I'm just using a DependencyProperty because I knocked this up in a UserControl and I'm just using a collection of strings for simplicity. You'll need to create a class that contains all of the data required for the whole TabItem content. Next, let's see the TabControl:
<TabControl ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ItemTemplate}" />
We data bind the collection to the TabControl.ItemsSource property and we set the TabControl.ItemTemplate to a Resource named ItemTemplate. Let's see that now:
xmlns:System="clr-namespace:System;assembly=mscorlib"
...
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type System:String}">
<TabItem Header="{Binding}" />
</DataTemplate>
This DataTemplate defines what each item in our collection will look like. For simplicity's sake, our strings are just data bound to the TabItem.Header property. This means that for each item we add into the collection, we'll now get a new TabItem with its Header property set to the value of the string:
Items.Add("Tab 1");
Items.Add("Tab 2");
Items.Add("Tab 3");
Note that I included the System XML Namespace Prefix for completeness, but you won't need that because your DataType will be your own custom class. You'll need more DataTemplates too. For example, if your custom class had a Header property and a Content property, which was another custom class, let's say called Content, that contained all of the properties for the TabItem.Content property, you could do this:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type YourPrefix:YourClass}">
<TabItem Header="{Binding Header}" Content="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:Content}">
<YourPrefix:SomeUserControl DataContext="{Binding}" />
</DataTemplate>
So this would give you TabItems with Headers set and Content that comes from SomeUserControl which you could design. You don't need to use UserControls, you could just add more UI controls to either DataTemplate. But you will need to add more controls somewhere... and more classes and properties, always remembering to correctly implement the essential INotifyPropertyChanged interface.
And finally, to answer your question in the proper MVVM way... to remove a TabItem, you simply remove the item that relates to that TabItem from the collection. Simple... or it would have been if you really had been using MVVM like you claim. It's really worth learning MVVM properly as you'll soon see the benefits. I'll leave you to find your own tutorials as there are many to chose from.
UPDATE >>>
Your event handling is still not so MVVM... you don't need to pass a reference of any view model anywhere. The MVVM way is to use commands in the view model. In particular, you should investigate the RelayCommand. I have my own version, but these commands enable us to perform actions from data bound Buttons and other UI controls using methods or inline delegates in the view model (where action and canExecute in this example are the CommandParameter values):
<Button Content="Close Tab" Command="{Binding CloseTabCommand}"
CommandParameter="{Binding}" />
...
public ICommand CloseTabCommand
{
get { return new ActionCommand(action => Items.Remove(action),
canExecute => canExecute != null && Items.Contains(canExecute)); }
}
So whatever view model has your Tabs collection should have an AddTabCommand and a CloseTabCommand that add and remove items from the Tabs collection. But just to be clear, for this to work properly, your ClosableTab class should be a data class and not a UI control class. Use a DataTemplate to specify it if it is a UI control.
You can find out about the RelayCommand from this article on MSDN.
I'm quite new to MVVM, and I've been constructing my ViewModels. I have a ViewModel which contains an ICommand, which is then bound to in my View by a command button. The ICommand causes a procedure to be invoked on my ViewModel which then invokes a further large slow procedure. While this procedure is happening I want to make a control/UIElement's visibility to become visible and then hidden after the procedure has finished (I intend to bind a label and indeterminate progress bar's visibility)
For example, in my view model I have
public void calledFromCommandButton() {
RaisePropertyChange("Starting");
superLongProcedure();
RaisePropertyChange("Finished");
}
This just feels a bit silly though, having to raise 2 different property changes and hence, I presume I'm doing it all wrong. I think I could do it with one property change along with a convertor?
So, what is the proper and correct method to bind UIElement visibilities to property change events?
Thanks
Thomas
I would recommend using a single boolean property (IsWorking or something) and then using the BooleanToVisibilityConverter to show and hide the button. So, it would look something like:
<Window ...>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="TrueToVisibleConverter"/>
</Window.Resources>
...
<Button x:Name="CancelButton" Content="Cancel" Visiblity="{Binding IsWorking, Converter={StaticResource TrueToVisibleConverter}}"/>
...
</Window/>
FINAL NOTE
Final solution found in another post
Although I appreciated the clarification that was provided, the ultimate solution was in-fact provided by another solution as linked above. No matter WHAT I tried, the binding via the "Element Name" component was not working. I had to go based on the "Relative" hierarchy up the datagrid...
<Button Name="btnPrintReport"
Command="{Binding DataContext.MyPrintCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}"
Height="16" Width="16" HorizontalAlignment="Center" >
<Image Source="MyButtonImage.png" IsHitTestVisible="True"/>
</Button>
Hope something not too complicated in WPF / MVVM environment. Here's the scenario.
I have a Window (.xaml) and a corresponding View Model (.cs). The form displays fine with all the data bindings no problem. (note: this is NOT done via any commercial "framework" )
One of the controls that is in the view window is a custom user control of a datagrid with all pre-defined columns, headings and content to be displayed when the view is shown. This works all no problem even though the control is not directly "defined" in the main window .xaml file, but just dropped on the form as the user control itself (which has its own obvious .cs code-behind).
With the main window's "DataContext" pointing to the View Model, and the user control that has a datagrid
<DataGrid AutoGenerateColumns="False"
Name="dataMyStuff"
ItemsSource="{Binding Path=MyTablePropertyOnViewModel,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}" ... />
Now, what I'm looking for. On this data grid, I have a column that has an image in the first column. When I click on this image, I want to print a report specific to the record as represented by this row (it has a PK value I use). So, how do I tell the image "KeyUp" event to go to the View Model event handler since that is where the data is, and some other methods I'll need for preparing the call to the report. The view portion of the grid is for cosmetic display to the user, and thus no "functionality" directly in this control.
-- EDIT -- per progress from answers
I've adjusted my datagrid per comments from Josh and Rachel, however, something still does not appear to be quite right... Seeing the button was using a "Command" instance, I interpreted this as it needed to attach to an instance of an "ICommand" interface object on my view model. So, I created an instance. I know the command handler works as it is also used for common things like Add, Edit, Save, Cancel, Exit, etc... So I have a new one for this printing purpose. For simplicity, I have it created as an Always Execute, so there is no method to handle the "CanExecute" portion of the control. I've set the button's "Command" to almost all iterations I could think of an still nothing, but here's an update of what I'm seeing going on.
<UserControl>
<Data grid columns / template, etc to the button>
<DataTemplate>
<Button Name="btnPrintReport"
Command="{Binding DataContext.MyPrintCommand}" >
<Image Source="myPrintImage.png"/>
</Button>
</DataTemplate>
</Data grid columns, etc>
</UserControl>
In my ViewModel class (myICommandButtonHandler inherits from ICommand)
private myICommandButtonHandler myPrintCommand;
public myICommandButtonHandler MyPrintCommand
{
get { if (myPrintCommand == null)
myPrintCommand = new myICommandButtonHandler(myPrint);
return myPrintCommand;
}
}
private void myPrint()
{
MessageBox.Show( "Doing the print job..." );
}
Now, what I'm seeing. During step through initialization of all the controls and such. I click menu item to call my Window to be displayed. FIRST, it creates an instance of the View Model controller. THEN, it calls the Window and passes in the View Model controller as parameter so it is immediately set at the Window level as the "DataContext" of the window. The main window then goes into it's "InitializeComponents" call and starts to build out all the other controls embedded, including this separate class that contains the data grid in question. At the constructor of this usercontrol (that has the datagrid), there is no "data context" set yet as the rest of the controls have not been initialized yet, and I don't know why / when the "bindings" apparently get "hooked" in. So, it appears that trying to do the binding to the data grid's command button are failing. HOWEVER, at run-time, the actual data IS updated in the grid, so I know that much is working.
So, the data grid has its "ItemsSource" set to a property of a "DataView" property on the view model, but the binding of the "button" doesn't appear to see the "MyPrintCommand" handler that I thought would get the hit.. and its action is to just display a message box (for now).
Usually I use an AttachedCommand Behavior which allows me to bind Events to ViewModel Commands. For example, you could use
<Image ...
local:CommandBehavior.Event="KeyUp"
local:CommandBehavior.Command="{Binding DataContext.PrintCommand, ElementName=dataMyStuff}"
local:CommandBehavior.CommandParameter="{Binding }"/>
I'd would recommend using a different event than KeyUp, since I don't think Images can have Keyboard focus so the KeyUp event will never get fired.
A better alternative is to use a Button and overwrite it's Template to be your Image. This will maintain the Click functionality, and give you access to Command and CommandParameter properties
<Button Command="{Binding DataContext.PrintCommand, ElementName=dataMyStuff}"
CommandParameter="{Binding }">
<Button.Template>
<Image ... />
</Button.Template>
</Button>
Also, the CommandParameter="{Binding }" will simply pass the current DataRow's DataContext (your data object) to the command
Change the data template to be a button that has a image as its content. Use the command and commandparameter properties on the button to call your printing method. You can declare your print command in your viewmodel, and bind to it. Your parameter could be the selected row in your datagrid.
I have a dependency property on a user control called "RefreshCommand" (ICommand type) that I want to bind to a Command in my main window.
If I write this in code, it works...
MainToolbar.RefreshCommand = (ICommand)this.CommandBindings[0].Command;
.. but how can I express that in XAML?
I would additionally like to refer to the command by its name as opposed to its index.
Thanks,
you can bind in XAML to other XAML Element through e.g.
The Element name:
RefreshCommand="{Binding ElementName=window, Path=CommandBindings[0].Command}"
On Properties of "yourself"
RefreshCommand="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=CommandBindings[0].Command}"
Upwards along the tree with AncestorType
RefreshCommand="{Binding Path=CommandBindings[0].Command, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
If the toolbar is a child from window, I suppose the third should work well.
I would recommend doing this then.
3 Classes
MainWindow.xaml (Window)
MainViewModel.cs (Class)
UserControl.xaml (UserControl)
Set the data context of the MainWindow to the ViewModel. The UserControl will inheret this data context unless you change it explicitly. Set up the controls on the user control / main window to bind to relay commands on the Main View Model.
This will give you the context you need without hard coding indexes (xxx.[0]) and still maintain a degree of separation.
I may be making this more complicated than necessary ... but here goes.
I have my MainPage.xaml, in there I have references to two other UserControl's, ResultsView, and DetailsView.
Inside of the ResultsView, I have a ListBox bound to an ObservableCollection of custom items, I have a DataTemplate that is rendering each item. The item has a CaseID, and when I click on it, it's displayed as a HyperlinkButton, I need a Command I've set in the MainPageViewModel to fire, which handles changing the visibility to hide the ResultsView, and show the DetailsView.
How do I bind the Command of the Hyperlinkbutton to the Command located in my MainPageViewModel?
Thanks in advance!
edit for clarification:
MainPage.xaml
<resultsView:ResultsView/>
<detailsView:DetailsView/>
Each of the two views (ResultsView & DetailsView) have their own ViewModel. So I'm going from my DataTemplate which resides in a ListBox inside my ResultsView, I need to go up to the MainPageViewModel, an extra step than your answer mentioned. Tried your method naming my MainPage.xaml to Name="mainPage", and using that as the ElementName in my HyperlinkButton, but no luck. I'll do some research on the RelativeSource option and see if I can make that work.
Thanks for your help so far.
edit 2: Forgot to add that the DataTemplate is in a ResourceDictionary, not in the ResultsView.
Well, it depends on the specific details, not all of which you've told us, but I can give you some examples.
Let's say that your DataTemplate for the custom items resides in the XAML of the ResultsView user control. That's a good spot for it but you might have put it in a resource dictionary.
As you say, the ListBox is bound to the collection of custom items. Let's further assume that the custom items collection is itself a property in your MainPageViewModel. And you've said clearly that the command you want to bind to is also in MainPageViewModel.
So the problem, which is a very common one, is that you are working inside a template associated with a binding to a collection, and so your DataContext is a custom item. It is no longer the main view-model itself. That's great as you show appropriate properties of the custom item like CaseID. But it's not great when you want to escape to the top-level of view-model.
If what I've said is true, then the ResultsView user control is in fact probably bound to the MainPageViewModel because you haven't "drilled into" the custom items collection yet. So, what you need to do is find a way using the binding syntax to reference the ResultsView user control from inside the DataTemplate for the ListBox. If you can do that, then you've escaped the collection.
There are two main approaches to do this:
ElementName syntax
RelativeSource syntax
I'll describe ElementName syntax and you can look up the other one.
Part 1) Name your ResultsView UserControl element like this:
<UserControl ....
Name="resultsView">
<!-- ... -->
Part 2) Inside your DataTemplate where you are defining the appearance of the hyperlink use the ElementName syntax to refer to that element:
<TextBlock>
<Hyperlink Command="{Binding DataContext.ItemDetailsCommand, ElementName=resultsView}"/>
</TextBlock>
So first we use ElementName to get the ResultsView UserControl element, and then we have a path with two pieces: the first piece is the DataContext property of the ResultsView which gives us the MainPageViewModel (yeah!), and then the property of the command we want to invoke.
That's one way to "escape" the binding and issue commands found at a higher level in the view-model.