I build an MVVM pattern TreeView with
-Root
--Item
---Subitem
When clicking on any of the TreeViewItems, I would like to display the details of the actual Object (Model) in an separate Window.
But I'm not sure how to access the data of the object.
private void TreeView_OnSelectedItemChanged(object sender, RoutedEventArgs e)
{
TreeViewItem tvi = e.OriginalSource as TreeViewItem;
MessageBox.Show(tvi.ToString());
}
I would not recommend of using TreeView_OnSelectedItemChanged in MVVM styled WPF applicaiton.
Define on on your ModelView binded a binding to IsSelected property of TreeeViewItem and you wil be always aware of selection,a nd can select the item of interest from the code, as well.
My prior answer was addressing more than what was asked.
Since you want to react on selection changing in the TreeView by displaying the details of the TreeViewItem's bound object, you could use Caliburn Micro's Action mechanism. You can hook up the SelectedItemChanged event of your TreeView to a method in your ViewModel.
For Example in your View:
<TreeView
ItemsSource="{Binding YourDataObjects}"
cal:Message.Attach="[Event SelectedItemChanged] = [Action OnSelectedItemChanged($this)]"/>
And in your ViewModel you will have this method:
public void OnSelectedItemChanged(YourDataObject selectedItem)
{
//Do something with the selected item here
}
If you have problems setting this up let me know.
In an MVVM pattern, the data associated to a a control is supposed to be in the DataContext dependency property.
In your ViewModel, create a dependency property of type TreeViewItem, and in the View bind the SelectedValuePath property of the TreeView to your new dependency property.
Related
Hi I am working on a WPF project in which I have a grid control . The itemsSource property of the grid control is bound to a datatable in my viewmodel. I am following the mvvm pattern, so my question is that I need to bind the selectedcell property of the grid control to a property in my view model class. Is it possible to determine the name of the column in which the cell resides by binding it to a property in the view model class. I know an event handler can be attached to the cell which would call a function in the code behind the view, but I dont wish to follow that approach since it would not be mvvm. Kindly help me with any suggestions.
In your XAML bind the CurrentCell property to a DataGridCellInfo in your View Model:
<DataGrid SelectionUnit="Cell"
SelectionMode="Single"
ItemsSource="{Binding MyDataTable}"
CurrentCell="{Binding SelectedCellInfo, Mode=OneWayToSource}"/>
Then in your View Model you can access the header from the bound object:
public DataGridCellInfo SelectedCellInfo
{
get { return _selectedCellInfo; }
set
{
_selectedCellInfo = value;
OnPropertyChanged("SelectedCellInfo");
_columnName = _selectedCellInfo.Column.Header;
}
}
I've run into the following problem:
I'm currently creating an on screen keyboard that is a usercontrol that has its own viewmodel.
<UserControl.DataContext>
<Binding Source="{StaticResource Locator}" Path="AlphaNumericKeyboard" />
</UserControl.DataContext>
I'm attempting to add a dependency property called KeyboardAlphaMode that can be toggled by other view models that are using this usercontrol
public static readonly DependencyProperty KeyboardAlphaModeProperty = DependencyProperty.Register("KeyboardAlphaMode",
typeof(UIKeyboardAlphaMode), typeof(AlphaNumericKeyboardView),
new FrameworkPropertyMetadata(UIKeyboardAlphaMode.LowerCase, new PropertyChangedCallback(KeyboardAlphaModeCallBack)));
private static void KeyboardAlphaModeCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e){ ... }
But, when I attempt to bind to this property from another view, the callback was never fired ..
<k:AlphaNumericKeyboardView x:Name="alphaNumericKeyboard" KeyboardAlphaMode="{Binding KeyboardAlphaMode, UpdateSourceTrigger=PropertyChanged}">
</k:AlphaNumericKeyboardView>
What am I missing here? a setter? trigger?
Or this is just a thought, can a usercontrol that has dependency be bound to a viewmodel? or does it have to be bound to itself?
Edit - 10/10/2014 # 1:31pm
After rethinking the entire solution i came up with the following scenario for my problem.
I binded the Dependency Property to the view's viewmodel and let the viewmodels interact with each other instead having other viewmodel talking to this specific view ...
Here's the code for that ..
Binding alphaModeBinding = new Binding("KeyboardAlphaMode")
{
Mode = BindingMode.TwoWay,
TargetNullValue = UIKeyboardAlphaMode.LowerCase,
FallbackValue = UIKeyboardAlphaMode.LowerCase
};
this.SetBinding(KeyboardAlphaModeProperty, alphaModeBinding);
I also made the dependency property protected so no one else can access it.
Unless there is a better way to track property changes, i'm sticking with this for now.
Again, not sure this is the best solution but it gets the job done.
Try Mode=TwoWay on the binding.
My view model implements IDataErrorInfo and contains a Message property that is validated.
I have created a UserControl with a Text DependencyProperty that is bound to Message. There are several controls on my UserControl that are bound to Text (which therefore show Message).
How can I show validation errors on the controls in my UserControl that are not bound to Message directly?
After quite some time, I have managed to figure out a solution that I thought I should share in case others find it useful:
Basically I have added a PropertyChangedCallback on my Text DependencyProperty. In this call-back I get the binding between Text and the property on the view model and check it for validation errors. If a ValidationError is found, I go through all the controls in my UserControl that are bound to Text, and give their binding the same error using Validation.MarkInvalid.
EDIT:
Copying the validation errors like this works fine if I put the code below in a button click event handler. If however the code is in the PropertyChangedCallback for Text then nothing happens. Does anyone have a solution?
// Get the binding from the Text property to the view model.
BindingExpression textBindingExpression = BindingOperations.GetBindingExpression(this,
MyUserControl.TextProperty);
// If there is a validation error, then give it to the control bindings.
if (textBindingExpression != null && textBindingExpression.ValidationError != null) {
Validation.MarkInvalid(this.MyTextBox.GetBindingExpression(TextBox.TextProperty),
textBindingExpression.ValidationError);
Validation.MarkInvalid(this.MyTextBlock.GetBindingExpression(TextBlock.TextProperty),
textBindingExpression.ValidationError);
}
Here is the solution I came up with which allows a UserControl with Dependency Properties to "wrap" the validation from the View Model it is bound to.
Firstly I followed the pattern in this post to create the desired DataContext hierarchy.
XAML:
<!-- Some boilerplate attributes snipped -->
<UserControl x:Class="App.Views.UserControls.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:App.Views.UserControls"
Validation.ErrorTemplate="{x:Null}">
<Grid x:Name="LayoutRoot"
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MyUserControl}}">
<TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>
This way, the DataContext of the control is the view model inherited from the parent, which is where the validation is done. This is then overridden on the control's root child element to be the control itself, which allows binding to the Dependency Properties in code-behind. Also note that the control's ErrorTemplate has been nulled out - this is to prevent the default red box appearing.
The inherited view model can now be accessed from the control's code behind quite simply:
private INotifyDataErrorInfo ViewModelErrors => DataContext as INotifyDataErrorInfo;
Now implement INotifyDataErrorInfo in the user control and wrap the view model:
public bool HasErrors => ViewModelErrors.HasErrors;
public IEnumerable GetErrors(string propertyName)
{
return ViewModelErrors.GetErrors(propertyName);
}
The tricky part comes when you need to know which model property your control dependency property is bound to. This would be easier if you could look up registered dependency properties by name and interrogate the binding, but I couldn't find a way to do that without reflection. Therefore I used the PropertyChangedCallback of the dependency property to manually build a list of mappings. The parameters to the callback contain all the required information.
// Maps User Control properties to their View Model properties.
private readonly Dictionary<string, string> _propertyMappings = new Dictionary<string, string>();
// This should work for any property.
private static void OnDependencyPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var userControl = (MyUserControl)d;
var dependencyPropertyName = e.Property.Name;
// Create this mapping one time only.
if (!userControl._propertyMappings.ContainsKey(dependencyPropertyName))
{
// Get the binding from the property to the view model.
var binding = BindingOperations.GetBindingExpression(d, e.Property);
if (binding != null)
{
// Create a mapping of user control property to view model property.
// This will let us look up the error from the view model.
var boundPropertyName = binding.ResolvedSourcePropertyName;
userControl._propertyMappings[dependencyPropertyName] = boundPropertyName;
}
}
}
Then incorporate this in to GetErrors:
public IEnumerable GetErrors(string propertyName)
{
if (ViewModelErrors != null && _propertyMappings.ContainsKey(propertyName))
{
return ViewModelErrors.GetErrors(_propertyMappings[propertyName]);
}
else
{
return Enumerable.Empty<string>();
}
}
That should be enough. Validation is done in the model and the results pulled down to the user control. No need to duplicate.
I'm binding my viewmodel and view using resource dictionary as follows
<DataTemplate DataType="{x:Type viewmodels:MyViewModel}">
<Views:MyView />
</DataTemplate>
in MyView, i have dataGrid x:Name="BoxDataGrid" with DataGrid.RowDetailsTemplate having other dataGrid x:Name="SpoolsDataGrid"
how to access MyView or datagrids above using code behind in MyViewModel ?
The reason is,i want to load and show contents inside RowDetailsTemplate only when main datagrid row selected (clicked) thru event "RowDetailsVisibilityChanged".
Thanks.
Correction:
My bad. I want to access MyView not MyViewModel
It's quite easy. DataContext property in your MyView object points to concrete object of MyViewModel. So you can use XAML bindings to this view-model or access in code-behind e.g.
MyViewModel model = (MyViewModel) DataContext;
asktomsk's answer is correct. You can access the ViewModel via the DataContext property.
However, with a little bit more effort you can almost always get around directly accessing the ViewModel from the view. The whole point of MVVM or MVC is that there aren't dependencies from View to ViewModel.
Things you should research in WPF for MVVM include:
Attached Properties
Attached Behaviors
Mediators
Value Converters
Markup Extensions
You need to be aware of all of these to find elegant solutions to some problems you encounter with MVVM.
You'll need to specify a bit more detail about the behaviour you are trying to get if you want us to help you figure out how you can do it without accessing the ViewModel through the datacontext.
You can, for instance, bind somethings' Visibility to a boolean in the ViewModel using a converter?
I apologize if you know all of the above already.
Just solved this problem using MVVM Light Toolkit - EventToCommand. Other better suggestions are very much welcome.
http://blog.galasoft.ch/archive/2009/11/05/mvvm-light-toolkit-v3-alpha-2-eventtocommand-behavior.aspx
Hope this solution will be useful to others.
don't need to know the view, in my datagrid view
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowDetailsVisibilityChanged">
<cmd:EventToCommand Command="{Binding RowDetailsVisibilityChangedCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
and in viewmodel
public RelayCommand<DataGridRowDetailsEventArgs> RowDetailsVisibilityChangedCommand
{
get;
private set;
}
and in viewmodel constructor
RowDetailsVisibilityChangedCommand = new RelayCommand<DataGridRowDetailsEventArgs>(e =>
{
DataGrid SpoolsDataGrid = e.DetailsElement as DataGrid;
DataRowView drv = (DataRowView)e.Row.Item;
serialNo = drv.Row["BOX_SERIAL"].ToString();
SpoolsDataGrid.ItemsSource = as400Service.GetSPOOL_BY_SERIAL_NO(serialNo);
});
Normally, to obtain the collection view of a control, I will call the following:
CollectionView cv = (CollectionView)CollectionViewSource.GetDefaultView(list.ItemsSource);
This is normally done in the code behind of the xaml file.
However, in MVVM, the ViewModel is not supposed to know about the existence of the View. How do I obtain the CollectionView of a control if I want to do it in MVVM fashion?
You can get the CollectionView in the ViewModel
1- You are having the Data Source of your list and you bind the item source of the list with this known Data Source.
2- Suppose DataSource is a DataTable named dt.
CollectionView cv = (CollectionView)CollectionViewSource.GetDefaultView(dt);
this will give you the CollectionView in ViewModel
you need to define the ItemsSource as a Property in the ViewModel like
public CollectionView _sourceForList;
public CollectionView SourceForList
{
get
{
return _sourceForList;
}
set
{
_sourceForList = value;
}
}
then in XAML you can bind this Property to List
<ListBox Margin="9,30,9,0"
Name="listBox1" ItemsSource="{Binding SourceForList}" }/>
and you can call like
CollectionView cv = (CollectionView)CollectionViewSource.GetDefaultView(SourceForList);
hope this helps
Retrieve the CollectionView in the code-behind of the xaml file (View). The MVVM pattern is not about eliminating the code-behind. It's about separation of concerns and testability.
The BookLibrary sample of the WPF Application Framework (WAF) shows how to work with the CollectionView for filtering in a MVVM application.