stuck to call view model from view - wpf

I have a tree view like below added mousedouble click
<TreeView
Grid.Row="0"
Name="tvTopics"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
MouseDoubleClick="tvTopics_MouseDoubleClick"
ItemsSource="{Binding TierOneItems}"
SelectedItemChanged="treeView1_SelectedItemChanged">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding Topic.IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
on my code behind
private void tvTopics_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
TreeView tv = sender as TreeView;
if (tv.SelectedItem is TopicTreeItemViewModel)
{
Model.SelectedTopic = ((TopicTreeItemViewModel)tv.SelectedItem).Topic;
}
}
here i am trying to pass my "topic" value to my view model but i have no idea how to pass or call my view model method.
public class TopicTreeViewModel : NotificationObject, ITopicTreeViewModel
{
[ImportingConstructor]
public TopicTreeViewModel(IGatewayService storyService, IEventAggregator eventAggregator)
{
this.storyService = storyService;
this.eventAggregator = eventAggregator;
this.AddTopicCommand = new DelegateCommand<object>(this.AddTopic);
Helper.SubscriptionTokenList_LocationSearch.Add(this.eventAggregator.GetEvent<LocationSearchEvent>().Subscribe(OnLocationSearch, ThreadOption.UIThread));
Helper.SubscriptionTokenList_SubjectSearch.Add(this.eventAggregator.GetEvent<SubjectSearchEvent>().Subscribe(OnSubjectSearch, ThreadOption.UIThread));
}
public void MouseDoubleClick(Topic topic)
{
if (topic != null && topic is Topic)
{
switch (this.searchType)
{
case SearchType.Location:
this.eventAggregator.GetEvent<AddLocationEvent>().Publish((Topic)topic);
break;
case SearchType.Subject:
this.eventAggregator.GetEvent<AddSubjectEvent>().Publish((Topic)topic);
break;
}
}
}
And the interface connect between view and view model
public interface ITopicTreeViewModel
{
ReadOnlyCollection<TopicTreeItemViewModel> TierOneItems { get; }
ICommand SearchCommand { get; }
string SearchText { get; set; }
Topic SelectedTopic { get; set; }
}
All im trying to do here is passing the topic value to my view model when the mousedouble click event triggered.
I have no idea how to pass or bind this value. any help much appreciated.

When using Prism and MVVM in particular, it is reccomended to add the minimal code behind implementation as possible. Therefore, every logic or action performed would be handled directly into the ViewModel.
Instead of handling the event on the View's Code Behind, you should bind the MouseDoubleClick event to a Delegate Command in the ViewModel. So, in order to achieve this, you would need to set the proper ViewModel as the DataContext of the View. This way, Binding would be resolved through the DataContext implementation.
The following MSDN Prism Guide chapter would be helpful to understand the interaction between View and ViewModel:
Implementing the MVVM pattern
Advance MVVM scenarios
In addition, you could take a look at the MVVM Prism QuickStart and undestand how the Binding to the View-ViewModel interaction is implemented.
I hope this helped, Regards.

Related

Prism : Navigation between different Usercontrols

I am using Prism. I have a view(DetailMainUC.xaml) which hold many other views in the following way.
<UserControl.Resources>
<DataTemplate x:Key="View1Template" DataType="{x:Type vm:DetailMainUCViewModel}">
<local:HomeUC />
</DataTemplate>
<DataTemplate x:Key="View2Template" DataType="{x:Type vm:DetailMainUCViewModel}">
<local:WalkAwayBehaviorUC />
</DataTemplate>
<DataTemplate x:Key="View3Template" DataType="{x:Type vm:DetailMainUCViewModel}">
<local:WakeUpOnApproachUC />
</DataTemplate>
<DataTemplate x:Key="View4Template" DataType="{x:Type vm:DetailMainUCViewModel}">
<local:NoLockOnPresenceUC />
</DataTemplate>
<DataTemplate x:Key="View5Template" DataType="{x:Type vm:DetailMainUCViewModel}">
<local:PeekDimmingUC />
</DataTemplate>
<DataTemplate x:Key="View6Template" DataType="{x:Type vm:DetailMainUCViewModel}">
<local:SettingsUC />
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl Grid.Column="1" Grid.Row="1" Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding SwitchView}" Value="Home">
<Setter Property="ContentTemplate" Value="{StaticResource View1Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding SwitchView}" Value="Walk Away Behaviour">
<Setter Property="ContentTemplate" Value="{StaticResource View2Template}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding SwitchView}" Value="Wake up on approach">
<Setter Property="ContentTemplate" Value="{StaticResource View3Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding SwitchView}" Value="No lock on presence">
<Setter Property="ContentTemplate" Value="{StaticResource View4Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding SwitchView}" Value="Peeking and Dimming">
<Setter Property="ContentTemplate" Value="{StaticResource View5Template}" />
</DataTrigger>
<DataTrigger Binding="{Binding SwitchView}" Value="Settings">
<Setter Property="ContentTemplate" Value="{StaticResource View6Template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Now I am using event aggregator to communicate between different view models in other regions. My view model for the above usercontrol is as follows:
public DetailMainUCViewModel(IEventAggregator eventAggregator, HomeViewModel homeViewModel)
{
this.HomeVM = homeViewModel;
this._eventAggregator = eventAggregator;
HomeVM.Initialization(this._eventAggregator);
this._eventAggregator.GetEvent<MenuClickEvent>().Subscribe(GetMenuName);
SwitchView = "Home";
}
Now the property HomeVM is of type HomeViewModel which is view model of child DetailMainUCViewModel. Like this there are many other child view models are there. Now my problem is I am seeing constructor of HomeViewModel is getting called twice and same is happening for all other child view models.
the main problem I am facing is while child view models are getting called second times , the eventAggregator is becoming null.
DetailsMainUCViewModel is parent viewmodel and HomeViewModel is child viewmodel. DetailMainUC.xaml holds the HomeUC.xaml in the way as mentioned in code section.
I have written the below mentioned code too
<UserControl.DataContext>
<vm:HomeViewModel></vm:HomeViewModel>
</UserControl.DataContext>
I suspect as I am using two places to attach datacontext so my viewmodel is getting called two times.
in
1.HomeUC.xaml
2.Parent usercontrol DetailMainUC.xaml
But I am unable to remove them as it is needed.
My class design is DetailMainUCViewModel is parent viewmodel and HomeViewModel is child viewmodel. DetailMainUC.xaml is parent view and HomeUC.xaml is child view. at the beginning the code snippet which I have given is from DetailMainUC.xaml (which holds all child usercontrols)
apart from this I am initializing my DetailMainUC.xaml in module where I have implemented IModule interface.
public class STDetailOptionsModule : IModule
{
private readonly IRegionManager _regionManager;
public STDetailOptionsModule(IRegionManager regionManager)
{
this._regionManager = regionManager;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion(RegionNames.DetailOptionsRegion, typeof(DetailMainUC));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
regards
If I have understood your question properly then I can say that you are using multiple usercontrols in same usercontrol to navigate by virtue of triggers.
This approach is not correct.
Basically you want a navigation between various usercontorls.
Let me explain how to do this in Prism.
this.regionManager.RequestNavigate("your region name where the usercontrol will be put ",
navigationPath, NavigationCompleted);
NavigationCompleted is a call back method:
navigationPath: is the usercontrol path which you want to place in a specified region.
Hope it is clear
As you said you are new to prism so let me explain vividly.
As I can see you have Views (WalkAwayBehaviorUC, WakeUpOnApproachUC, NoLockOnPresenceUC,PeekDimmingUC, SettingsUC).
Now let me consider these views are in different projects or in same project but you want them to be displayed in your region that is RegionNames.DetailOptionsRegion.
Now to do that , you have to put all navigation logic in a centralized location.
the best centralized location could be shell . So ShellViewModel.cs is the class where you have to write navigation logic.
So create a method like this :
private void NavigationInitialized(string navigationPath)
{
if (string.IsNullOrEmpty(navigationPath))
{
throw new Exception();//or send proper message to UI
}
this.regionManager.RequestNavigate(RegionNames.DetailOptionsRegion,
navigationPath, NavigationCompleted);
}
private void NavigationCompleted(NavigationResult navigationResult)
{
}
Now the question is how to get this navigation name in ShellViewModel.cs.
The solution is EventAggregator.
In the place where you want the usercontrol should navigate write.
this.eventAggregator.GetEvent<Navigated>().Publish("WalkAwayBehaviorUC");
now the Navigated event class should be like :
public class Navigated : PubSubEvent<string>
{
}
Now you have to subscribe this Navigated Event in ShellViewModel.cs
So in constructor of ShellViewModel.cs you have to write:
this.eventAggregator.GetEvent<Navigated>().Subscribe(NavigationInitialized);

Avalondock close document with MVVM

We have a working avalondock implementation that listens to onclosing events, if the document is not saved the user gets a chance to save it etc. Works well.
Now a user wants a close button from the File menu and it should work like the built in close button (The little X by the document name).
Only way I have find is not very MVVM friendly.
I databind the CloseCommand to the dockable items ViewModel like
<Setter Property="CloseCommand" Value="{ Binding Model.CloseCommand, Mode=TwoWay}" />
Then from the ViewModel i have a method
public ICommand CloseCommand { get; set; }
public void Close()
{
if (CloseCommand.CanExecute(this))
{
CloseCommand.Execute(this);
}
}
This works and all the behaviour from pressing the built in close button is retained. But I think its a ugly hack. I'm dependant on that the View databinds the CloseCommand down to the viewmodel etc. There must be a more MVVM way of triggering close?
I solved it like this
VM
public ICommand CloseCommand { get; set; }
public void Close()
{
if (CloseCommand.CanExecute(this))
{
CloseCommand.Execute(this);
}
}
View
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.Title}" />
<Setter Property="IconSource" Value="{Binding Model.Icon}"/>
<Setter Property="IsActive" Value="{Binding Model.IsActive, Mode=TwoWay}"/>
<Setter Property="ContentId" Value="{Binding Model.ContentId}"/>
<Setter Property="Visibility" Value="{Binding Model.IsVisible, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter={x:Static Visibility.Hidden}}"/>
<Setter Property="CloseCommand" Value="{ Binding Model.CloseCommand, Mode=TwoWay}" />
</Style>
</xcad:DockingManager.LayoutItemContainerStyle>

Selecting User Control for Data Template based on an Enum

I am working on a WPF app and currently I have an ItemsControl bound up to my View Model ObservableCollection and I have a DataTemplate that uses a UserControl to render the items on canvas. Can you use multiple User Controls and then switch which one is used based on an Enum? Another way to look it is to either create a Button or a TextBox for the item in the ObservableCollection based on an Enum.
You can select the data template for an item using a custom DataTemplateSelector. Assume we have the following:
public enum Kind
{
Button, TextBox,
}
public class Data
{
public Kind Kind { get; set; }
public string Value { get; set; }
}
Your data template selector might then look like this:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate ButtonTemplate { get; set; }
public DataTemplate TextBoxTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Data data = (Data)item;
switch (data.Kind)
{
case Kind.Button:
return ButtonTemplate;
case Kind.TextBox:
return TextBoxTemplate;
}
return base.SelectTemplate(item, container);
}
}
In XAML, declare templates for all the cases you want to cover, in this case buttons and text boxes:
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="ButtonTemplate" DataType="local:Data">
<Button Content="{Binding Value}" />
</DataTemplate>
<DataTemplate x:Key="TextBoxTemplate" DataType="local:Data">
<TextBox Text="{Binding Value}" />
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
Finally, have your ItemsControl create an instance of your custom template selector, initializing its two DataTemplateproperties from the above data templates:
<ItemsControl>
<ItemsControl.ItemTemplateSelector>
<local:MyTemplateSelector
ButtonTemplate="{StaticResource ButtonTemplate}"
TextBoxTemplate="{StaticResource TextBoxTemplate}"/>
</ItemsControl.ItemTemplateSelector>
<ItemsControl.Items>
<local:Data Kind="Button" Value="1. Button" />
<local:Data Kind="TextBox" Value="2. TextBox" />
<local:Data Kind="TextBox" Value="3. TextBox" />
<local:Data Kind="Button" Value="4. Button" />
</ItemsControl.Items>
</ItemsControl>
(In real life, set the ItemsSource instead of declaring the items inline, as I did.)
For completeness: To access your C# classes you need to set up the namespace, e.g.,
xmlns:local="clr-namespace:WPF"
Another possible quick solution is to use Data Triggers:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content"
Value="{StaticResource YourDefaultLayout}" />
<Style.Triggers>
<DataTrigger Binding="{Binding YourEnumVMProperty}"
Value="{x:Static local:YourEnum.EnumValue1}">
<Setter Property="Content"
Value="{StaticResource ContentForEnumValue1}" />
</DataTrigger>
<DataTrigger Binding="{Binding YourEnumVMProperty}"
Value="{x:Static local:YourEnum.EnumValue2}">
<Setter Property="Content"
Value="{StaticResource ContentForEnumValue2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
You could also define the template of a whole control using a trigger setter.
I prefer this because there is no need to define all the DataTemplateSelector stuff etc.

Having a nested ViewModel and want to bubble up a command to the owning viewmodel

I have a WPF application with a workarea that resembles a file system on a computer. I have a ViewModel that holds the top folder and a list of subfolders:
public interface IRepositoryViewModel : IViewModelBase
{
ObservableCollection<IRepositoryTreeFolderModel> RootFolders { get; set; }
}
The IRepositoryTreeFolderModel is described like this:
public interface IRepositoryTreeFolderModel : IViewModelBase
{
Folder Folder { get; set; }
ObservableCollection<IRepositoryTreeFolderModel> SubFolders { get; set; }
bool IsSelected {get;set;}
bool IsExpanded {get;set;}
}
Please note that this is "recursive" with the same viewmodel nested. The purpose of this is to represent a tree structure of folders. I represent the data structure using a treeview, everything is working perfect. Now i need to have my "outer" IRepositoryViewModel know when i select a Folder in the tree. This is where i think im missing something. What im trying to do is to make a RoutedCommand, and consume it in my outer "IRepositoryViewModel"
My XAML for the treeView in the workarea is like this:
<TreeView Background="{x:Null}" ItemsSource="{Binding RootFolders}" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubFolders}">
<StackPanel Orientation="Horizontal">
<Image Source="/GWManagerAdmin;component/Graphics/Navigation/folder.png" Stretch="None" />
<TextBlock Text="{Binding Path=Folder.Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Do i need to raise my RoutedEvent in my IsSelected implementation manually (how to do this?) or is there a better approach for doing this. The problem here is that i do now know which instance of the IRepositoryTreeFolderModel has been selected, so i cant wire up a classic eventhandler and propagate it up - would also like to utilize the full potential of the RoutedCommand - i feel this is what I should do in this scenario...
Use Dan Wahlin's DataContext Proxy for this.
Just some idea from me. First you can read this http://joyfulwpf.blogspot.com/2009/05/mvvm-invoking-command-on-attached-event.html to build up yours.
In XAML, you can invoke command from parent by this snippet:
Command={Binding RelativeResource={RelativeResource FindAncestor, AncestorType={x:Type TreeView}}, Path=DataContext.YourCommand}
CommandArgument={Binding}
Hope it helps. I have not implemented myself. Just an idea.

WPF: Binding to a (observable) Dictionary

I'm using this ObservableCollection-Class within my Project: Link
I want to Bind a RibbonMenuButton to a ObservableDictionary<string,bool>:
<r:RibbonMenuButton ItemsSource="{Binding MyDictionary}">
<r:RibbonMenuButton.ItemContainerStyle>
<Style TargetType="{x:Type r:RibbonMenuItem}">
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Header" Value="{Binding Path=Key}"/>
<Setter Property="IsChecked" Value="{Binding Path=Value}"/>
</style>
</r:RibbonMenuButton.ItemContainerStyle>
</r:RibbonMenuButton>
But I get exceptions because the Value-Properties of the internal IDictionary-KeyValuePairs are readonly. Any Idea how to solve this?
I thought about something like:
<Setter Property="IsChecked" Value="{Binding Source=MyDictionary[{Binding Path=Key}]}"/>
But this won't work 'cause of {Binding} in {Binding}...
This doesn't work, because your dictionary isn't treated as a dictionary but as an IEnumerable<KeyValuePair<string, bool>>. So each RibbonMenuItem is bound to a KeyValuePair<string, bool> with readonly properties Key and Value.
You can do two one things:
1. Use an ObservableCollection<Tuple<string, bool>> instead of the dictionary and bind IsChecked to Item2.
2. Create a little helper class that contains a IsChecked property and change your dictionary to contain that class as the value and bind IsChecked to Value.IsChecked.
I would go with answer two, because the needed changes and possible side effects are smaller.
My answer assumes that you want to have a two way binding on IsChecked. If not, go with the answer of slugster.
WPF binding is two-way by default. Make it one-way and see if that solves your issue.
<r:RibbonMenuButton ItemsSource="{Binding MyDictionary}">
<r:RibbonMenuButton.ItemContainerStyle>
<Style TargetType="{x:Type r:RibbonMenuItem}">
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Header" Value="{Binding Key, Mode=OneWay}"/>
<Setter Property="IsChecked" Value="{Binding Value, Mode=OneWay}"/>
</style>
</r:RibbonMenuButton.ItemContainerStyle>
</r:RibbonMenuButton>
Here is a reference for you: MSDN Windows Presentation Foundation Data Binding: Part 1 (specifically check the section Binding Mode close to the bottom of the page)
If You want to bind MenuItems to Dictionary<string, bool> without using a helper class, like the accepted answer suggests, here is the minimal-change solution (no need to add anything else):
define a Click event inside the ItemContainerStyle whose ClickEventHandler will update the dicitonary.
declare a dictionary and initialize it inside the UserControl's / Window's constructor
In code:
MainWindow.xaml:
<MenuItem Header="_My settings" ItemsSource="{Binding MySettings}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsCheckable" Value="true"/>
<Setter Property="Header" Value="{Binding Key, Mode=OneWay}"/>
<Setter Property="IsChecked" Value="{Binding Value, Mode=OneWay}"/>
<!-- this is the main line of code -->
<EventSetter Event="Click" Handler="MySettings_ItemClick"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
// properties...
// Declaration of the dictionary
public Dictionary<string, bool> MySettings{ get; set; }
public MainWindow()
{
InitializeComponent();
// Initialize the dictionary
MySettings = new Dictionary<string, bool>()
{
{ "SettingOne", true}
// Other pairs..
};
}
// other things..
// ClickEvent hanlder
private void MySettings_ItemClick(object sender, RoutedEventArgs e)
{
MenuItem clickedItem = (sender as MenuItem);
MySettings[clickedItem.Header as string] = clickedItem.IsChecked;
}
} // end of MainWindow class
That's it! You're all set!
Credits to slugster and his answer for XAML code for OneWay binding :)
As a general solution to this problem of binding to dictionaries I created an UpdateableKeyValuePair and return that instaed of the usual KeyValuePair. Here is my class:
public class UpdateableKeyValuePair<TKey,TValue>
{
private IDictionary<TKey, TValue> _owner;
private TKey _key;
public UpdateableKeyValuePair(IDictionary<TKey, TValue> Owner, TKey Key_)
{
_owner = Owner;
_key = Key_;
}
public TKey Key
{
get
{
return _key;
}
}
public TValue Value
{
get
{
return _owner[_key];
}
set
{
_owner[_key] = value;
}
}
}

Resources