Avalondock close document with MVVM - wpf

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>

Related

WPF Concatenated DataTrigger Binding

In my application i'd like to change the Background property of a TextBox when the <Property>IsChanged property of my Model is set to True
I have been able to successfully do this for one of my TextBox controls with the following style:
Notice the hard-coded binding for FirstNameIsChanged in the DataTrigger
<TextBox.Style>
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<DataTrigger Binding="{Binding FirstNameIsChanged}" Value="True">
<Setter Property="Background" Value="SteelBlue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
If my application has a form with 10 or so TextBox controls (FirstName, MiddleName, LastName, Age, Gender etc.) i don't want to copy and paste the above code if i'm only changing the Binding on the DataTrigger for each property.
I've researched using MultiBinding in combination with RelativeSource and StringFormat. I'm trying to create a binding that gets the Tag property of the TextBox and adds "IsChanged" to the name but i have not managed to get it working (I don't see any binding warnings/errors in Visual Studio either)
This is the Style i'm trying to create:
<Style x:Key="TextBoxTestStyle" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding StringFormat="\{0\}IsChanged">
<Binding Path="Tag"
RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TextBox}}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
Each of the Properties on my Model have their own IsChanged property that i'm using for Change Tracking, this is a quick preview of my Model and how i've made the IsChanged properties:
public class Person
{
public string FirstName { get; set; }
public bool FirstNameIsChanged { get; set; }
public string MiddleName { get; set; }
public bool MiddleNameIsChanged { get; set; }
public string LastName { get; set; }
public bool LastNameIsChanged { get; set; }
public int Age { get; set; }
public bool AgeIsChanged { get; set; }
public DateTime? DateOfBirth { get; set; }
public bool DateOfBirthIsChanged { get; set; }
}
As an example, if i have the following TextBox and i set the Tag property to "FirstName", the style should set the Background property to "Red" if the FirstNameIsChanged property is set to True :
<TextBox x:Name="FirstNameTextBox"
Tag="FirstName"
Margin="3"
Height="23"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Style="{DynamicResource TextBoxTestStyle}"
Text="{Binding FirstName,
Mode=TwoWay,
NotifyOnValidationError=true,
ValidatesOnExceptions=true,
UpdateSourceTrigger=PropertyChanged}"/>
The style (If it's codable) would allow me to set the Tag property on 10 TextBox controls but only have 1 style for all my TextBox controls instead of having to copy and paste the style 10 times. I also want to avoid adding "Boiler plate" code if i decide to add additional properties to my Model in the future such as an IsValid property or an IsEnabled property.
Some ideas i've had:
Can i add "IsChanged" to the string value of the Tag property and use the resulting string in a binding? e.g. Tag("FirstName") + "IsChanged" = "FirstNameIsChanged"
From a Style can i walk up the visual tree and grab the Binding that is already on the Text property and add "IsChanged" to the path? e.g. BindingPath("FirstName") + "IsChanged" = "FirstNameIsChanged"
You shouldn't be comparing to a boolean. Also, to make it Generic, I would make the Tag TextBox-independent, e.g. IsChanged:
<Style x:Key="TextBoxTestStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Tag" Value="IsChanged">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
EDIT
To preserve to Booleans you could use a less generic solution. Since Tag expects a string I added a Converter (you could also create another Dependeny Property expecting a boolean).
<....Resources>
<local:BoolToStringConverter x:Key="BoolToStringConverter"/>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Tag" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</....Resources>
...
<TextBox
x:Name="FirstNameTextBox"
Tag="{Binding FirstNameIsChanged,
Converter={StaticResource BoolToStringConverter}}"
Style="{StaticResource TextBoxStyle}"
...
/>
<TextBox
x:Name="MiddleNameTextBox"
Tag="{Binding MiddleNameIsChanged,
Converter={StaticResource BoolToStringConverter}}"
Style="{StaticResource TextBoxStyle}"
...
/>
...
Without reaching for code-behind (see my comment), this is probably the shortest solution. If you're not using the TextBox Tag Property for anything else, you could also make the Style implicit.

stuck to call view model from view

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.

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 MVVM ContextMenu Bound to ObservableCollection<string> Command Not Firing

I am trying to bind an ObservableCollection to a ContextMenu using MVVM. But when i try to fire the command nothing is happening. also, i need to pass the string as command parameter to the event.
Below is the xaml code:
<ContextMenu Name="ctxAddApplication" ItemsSource="{Binding Path=ApplicationTypes}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding AddRequirementCommand}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Below is the View Model Code:
public ObservableCollection<string> ApplicationTypes { get; private set; }
public ComposableCommand AddRequirementCommand { get; private set; }
this.AddRequirementCommand = new ComposableCommand(this.AddRequirementView);
private void AddRequirementView(object applicationName) {}
Please help !!!
Just in case you need the code:
<ContextMenu Name="ctxAddApplication" ItemsSource="{Binding Path=ApplicationTypes}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.AddRequirementCommand}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
The data context for each menu item will be whatever it is bound to. In your case, a string because your ApplicationTypes property is a collection of strings. Thus, your binding to set the command won't work because there is no AddRequirementCommand property on type String.
Inside ContextMenu view for each item is bound to the item from the collection.
<Setter Property="Command" Value="{Binding AddRequirementCommand}" />
this will try to locate 'AddRequirementCommand' in string class. Use RelativeSource in this Binding. Also use VS debugger and Output window to see binding errors, it helps a lot usually.

WPF MVVM Dynamic SubMenu Binding Issue

I'm dynamically creating a context menu and the menu items have children.
The first time around the submenus appear, but on the second and there after only the parent menus show. The child submenu are in the collection that is bound to the context menu they just don't appear.
VMMenuItems is a property in my view model and is
ObservableCollection<MenuItemViewModel>
Every time the data in the Listview changes VMMenuItems is totally rebuilt.
A sub menu is just adding another MenuItemViewModel to an existing MenuItemViewModel's Children.
Any ideas as to how to make the submenus work every time?
The code
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}"
ItemsSource="{Binding Path=Children}">
<ContentPresenter Content="{Binding Path=MenuText}" />
</HierarchicalDataTemplate>
</Window.Resources>
<ListView.ContextMenu>
<ContextMenu ItemsSource="{Binding Path=VMMenuItems>
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding MenuText}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</ListView.ContextMenu>
public class MenuItemViewModel : ViewModel
{
public MenuItemViewModel()
{
Children = new ObservableCollection<MenuItemViewModel>();
}
private string _menuText;
public string MenuText
{
get { return _menuText; }
set
{
_menuText = value;
OnPropertyChanged("MenuText");
}
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
private ICommand _command;
public ICommand Command
{
get { return _command; }
set
{
_command = value;
OnPropertyChanged("Command");
}
}
private ObservableCollection<MenuItemViewModel> _children;
public ObservableCollection<MenuItemViewModel> Children
{
get { return _children; }
set
{
_children = value;
OnPropertyChanged("Children");
}
}
I had to not use a HierarchicalDataTemplate and put it all here in ContextMenu.ItemContainerStyle.
I'm not sure why my other way didn't work( well it worked the 1st time but not any others).
Maybe someone else could tell me why it doesn't work...
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding MenuText}"/>
<Setter Property="ItemsSource" Value="{Binding Path=Children}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding MenuText}"/>
</Style>
</ContextMenu.ItemContainerStyle>
I'm still new to this myself and I don't know for sure without testing it or exactly why, but I believe it has to do with replacing the Children collection with an entirely new collection. I think that would require rebinding the collection. It would be better for items to be added/removed from the existing collection. This would trigger the appropriate binding notifications. Right now, the binding is to a particular instance of that collection which is getting replaced on the Children.set call. Hope this helps.

Resources