WPF binding of property with multiple usercontrols level - wpf

I have two usercontrols (LoginView.xaml and DashboardView.xaml).
DashboardView.xaml is placed inside LoginView.xaml .
Now, my objective is initially DashboardView should be invisible and on login success it should be visible and the controls in stackpanel "loginSP" should be invisible.
LoginView.xaml is as follows :-
<UserControl x:Class="DashboardModule.LoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DashboardModule"
xmlns:converter="clr-namespace:Matrix.Infrastructure.Framework.Utility;assembly=Matrix.Infrastructure"
mc:Ignorable="d"
x:Name="loginUC"
d:DesignHeight="450" d:DesignWidth="800" Background="Pink">
<UserControl.Resources>
<ResourceDictionary>
<converter:BooleanToVisibilityConverter x:Key="BoolVisibilityConverter"></converter:BooleanToVisibilityConverter>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Margin="200">
<StackPanel x:Name="loginSP">
<TextBlock Text="Dashboard" ></TextBlock>
<TextBox x:Name="tbUserName" Height="25" Width="300" ></TextBox>
<PasswordBox Margin="2"></PasswordBox>
<Button Content="Login" Command="{Binding LoginCommand}" CommandParameter="{Binding Path=Text,ElementName=tbUserName}" Width="200"
></Button>
</StackPanel>
<ContentControl>
<ContentControl.Template>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid>
<Border >
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</ContentControl.Template>
<local:DashboardView Visibility="{Binding DashboardVisible, Converter={StaticResource BoolVisibilityConverter}}"/>
</ContentControl>
</StackPanel>
DashboardView.xaml is as follows :-
<UserControl x:Class="DashboardModule.DashboardView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DashboardModule"
xmlns:converter="clr-namespace:Matrix.Infrastructure.Framework.Utility;assembly=Matrix.Infrastructure"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Background="BlanchedAlmond" x:Name="DashboardUC"
Visibility="{Binding Visibility,ElementName=rootGrd}">
<UserControl.Resources>
<ResourceDictionary>
<converter:BooleanToVisibilityConverter x:Key="BoolVisibilityConverter"></converter:BooleanToVisibilityConverter>
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="rootGrd" ToolTip="{Binding MyName, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding DashboardVisible,Converter={StaticResource BoolVisibilityConverter}}">
<TextBlock Text="{Binding MyName, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"></TextBlock>
</Grid>
My LoginViewModel.cs is as follows :--
private readonly IEventAggregator eventAggregator;
public DelegateCommand<string> LoginCommand { get; set; }
public DashboardViewModel DashborardVM { get; set; }
public LoginViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
DashborardVM = new DashboardViewModel();
LoginCommand = new DelegateCommand<string>(Login, IsValid);
}
private void Login(string obj)
{
DashborardVM.MyName = "My Name After Login";
DashborardVM.DashboardVisible = true;
//this.eventAggregator.GetEvent<ShellLayoutChangeEvent>().Publish("anindya");
}
private bool IsValid(string param)
{
if (param.Length > 0)
My code of DashboardViewModel.cs is as follows :-
private bool _dashboardVisible;
public bool DashboardVisible
{
get
{
return _dashboardVisible;
}
set
{
_dashboardVisible = value;
OnPropertyChanged("DashboardVisible");
}
}
private string myName;
public string MyName
{
get
{
return myName;
}
set
{
myName = value;
OnPropertyChanged("MyName");
}
}
public DashboardViewModel()
{
MyName = "anindya";
}
Now the output is :
1.Initially DashboardView is invisible .
2. On login success DashboardView is not getting visible although I am setting DashborardVM.DashboardVisible = true; on my loginviewmode.cs.
I am using Prism design pattern.
the following way I am registering the views and viewmodel.
public class ModuleDashboardModule: IModule
{
IUnityContainer _container;
IRegionManager _regionManager;
public ModuleDashboardModule(IUnityContainer container,
IRegionManager regionManager)
{
_container = container;
_regionManager = regionManager;
}
public void Initialize()
{
_container.RegisterType<ILoginView, LoginView>();
_container.RegisterType<ILoginViewModel, LoginViewModel>();
_container.RegisterType<IDashboardView, DashboardView>();
_container.RegisterType<IDashboardViewModel, DashboardViewModel>();
_regionManager.RegisterViewWithRegion(RegionNames.DashboardRegion,
typeof(LoginView));
_regionManager.RegisterViewWithRegion(RegionNames.DashboardRegion,
typeof(DashboardView));
}
}
this is my bootstrapper.cs class :--
public class BootStrapper : UnityBootstrapper,IDisposable
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
protected override void InitializeShell()
{
base.InitializeShell();
App.Current.MainWindow = (Window)Shell;
App.Current.MainWindow.Show();
}
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
Type moduleAType = typeof(ModuleDetailsModule);
Type moduleNewForm = typeof(ModuleNewFormModule);
Type moduleToolbarType = typeof(ModuleToolbarModule);
Type moduleFooterType = typeof(ModuleFooterModule);
Type moduleDashboardType = typeof(ModuleDashboardModule);
Type moduleInvestmentType = typeof(ModuleInvestmentModule);
Type moduleInvestmentDetailsType = typeof(ModuleInvestmentDetailsModule);
ModuleCatalog.AddModule
(
new ModuleInfo()
{
ModuleName = moduleAType.Name,
ModuleType = moduleAType.AssemblyQualifiedName,
InitializationMode = InitializationMode.WhenAvailable
}
);
ModuleCatalog.AddModule
(
new ModuleInfo()
{
ModuleName = moduleNewForm.Name,
ModuleType = moduleNewForm.AssemblyQualifiedName,
InitializationMode = InitializationMode.WhenAvailable
}
);
ModuleCatalog.AddModule
(
new ModuleInfo()
{
ModuleName = moduleToolbarType.Name,
ModuleType = moduleToolbarType.AssemblyQualifiedName,
InitializationMode = InitializationMode.WhenAvailable
}
);
ModuleCatalog.AddModule
(
new ModuleInfo()
{
ModuleName = moduleFooterType.Name,
ModuleType = moduleFooterType.AssemblyQualifiedName,
InitializationMode = InitializationMode.WhenAvailable
}
);
ModuleCatalog.AddModule
(
new ModuleInfo()
{
ModuleName = moduleDashboardType.Name,
ModuleType = moduleDashboardType.AssemblyQualifiedName,
InitializationMode = InitializationMode.WhenAvailable
}
);
ModuleCatalog.AddModule
(
new ModuleInfo()
{
ModuleName = moduleInvestmentType.Name,
ModuleType = moduleInvestmentType.AssemblyQualifiedName,
InitializationMode = InitializationMode.WhenAvailable
}
);
ModuleCatalog.AddModule
(
new ModuleInfo()
{
ModuleName = moduleInvestmentDetailsType.Name,
ModuleType = moduleInvestmentDetailsType.AssemblyQualifiedName,
InitializationMode = InitializationMode.WhenAvailable
}
);
}
My LoginView.xaml.cs file is :
public partial class LoginView : UserControl,ILoginView
{
[InjectionConstructor]
public LoginView(LoginViewModel viewModel)
{
InitializeComponent();
this.ViewModel = viewModel;
}
public IViewModel ViewModel
{
get
{
return (IViewModel)DataContext;
}
set
{
DataContext = value;
}
}
}
My DashboardView.xaml.cs is as follows:
public partial class DashboardView : UserControl,IDashboardView
{
DashboardViewModel viewModel = new DashboardViewModel();
[InjectionConstructor]
public DashboardView()
{
InitializeComponent();
this.DataContext = viewModel;
//this.ViewModel = viewModel;
}
public IViewModel ViewModel
{
get
{
return (IViewModel)DataContext;
}
set
{
DataContext = value;
}
}
}
I am not getting where I am making mistake . Any help is appreciable .

I would try to avoid handling Visibility too much in a ViewModel.
Instead, expose a bool e.g. LoggedIn and bind to it in your View.
There you can use a Style/DataTrigger to change Visibilies accordingly.
<Style x:Key="LoginVisibilityStyle" TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding LoggedIn}" Value="false">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding LoggedIn}" Value="true">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
if you have to go for a different Controls DataContext i.e. ViewModel, you can do this:
="{Binding DataContext.LoggedIn, UpdateSourceTrigger=PropertyChanged, RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type views:MainView}}}"

This is all about setting the correct data context.
Seems you are not setting the datacontext for the dashboard view. Therefore the loginViewModel becomes the datacontext of the dashboardview. This is because the datacontext is inherited from the xaml's parent control if not set explicitly.
Try setting the data context for content control
<ContentControl DataContext="{Binding DashborardVM}">
<ContentControl.Template>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid>
<Border >
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</ContentControl.Template>
<local:DashboardView Visibility="{Binding DashboardVisible, Converter={StaticResource BoolVisibilityConverter}}"/>
</ContentControl>
Otherwise you will have to make all the bindings in the format
{Binding DashborardVM.DashboardVisible}
{Binding DashborardVM.property}
EDIT -
By the way if you are using prism please check whether the viewmodel is correctly registered so they get automatically assigned.
And probably setting the visibility as
<local:DashboardView Visibility="{Binding DashborardVM.DashboardVisible, Converter={StaticResource BoolVisibilityConverter}}"/>
will help because here the datacontext is LoginViewmodel

Related

WPF validation error style in reuseable UserControl?

I have made a reusable UserControl in my WPF application that has a Textbox (and in the real project other components) inside of it. I'm using Fluent Validation with INotifyDataErrorInfo to validate user input in TextBoxes. My problem is that when the model whose property is bound to the UserControl's TextBox has errors, the TextBox's style doesn't trigger according to the style that is set. It seems that my style's trigger for the UserControl's Textbox can't read the Validation.HasError value from the model correctly. So is there a way to get the style to trigger and get the error tooltip for the UserControl's Textbox?
This question has been asked by several other people over the years, and I have looked at every single one of them, but none of them really work for me. One thing I tried that does work is a general ValidationRule in the UserControl.xaml for the textbox binding, but that doesn't allow for model specific rules. I'm hoping that some WPF guru will finally take the challenge and solve this problem! :)
If you make a sample project from the code I provided, and set the Height property to less than 10, you see that the normal TextBox gets the triggered errorstyle with the tooltip, but the UserControl's TextBox gets the basic red border:
Sample app with the cursor over the first textbox.
Here is my simplified code:
The UserControl:
<UserControl x:Class="UserControlValidationTest.DataInputUserControl"
x:Name="parentControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="TextBox" x:Key="TextBoxStyle">
<Style.Triggers>
<Trigger Property= "Validation.HasError" Value="true">
<Setter Property="Background" Value="Pink"/>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=parentControl}">
<TextBox Name="ValueBox" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" Width="60" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
public partial class DataInputUserControl : UserControl
{
public DataInputUserControl()
{
InitializeComponent();
Validation.SetValidationAdornerSite(this, ValueBox);
}
public double Value
{
get => (double)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(DataInputUserControl), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
MainWindow.xaml:
<Window x:Class="UserControlValidationTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UserControlValidationTest"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="200">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property= "Validation.HasError" Value="true">
<Setter Property="Background" Value="Pink"/>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Text="{Binding User.Height, UpdateSourceTrigger=PropertyChanged}" Width="60" Margin="10"/>
<local:DataInputUserControl Value="{Binding User.Height}" HorizontalAlignment="Center"/>
</StackPanel>
View model:
public class MainWindowViewModel
{
public UserModel User { get; set; }
public MainWindowViewModel()
{
User = new UserModel();
}
}
User Model:
public class UserModel : ValidatableModel
{
public double Height { get => GetPropertyValue<double>(); set => SetPropertyValue(value); }
public UserModel()
{
ModelValidator = new UserValidator();
Height = 180;
}
}
User Validator:
public class UserValidator : AbstractValidator<UserModel>
{
public UserValidator()
{
RuleFor(x => x.Height)
.GreaterThan(10);
}
}
Validatable Model:
using FluentValidation;
using FluentValidation.Results;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
public abstract class ValidatableModel : INotifyDataErrorInfo, INotifyPropertyChanged
{
private readonly Dictionary<string, object> propertyBackingDictionary = new Dictionary<string, object>();
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string parameter = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(parameter));
}
protected T GetPropertyValue<T>([CallerMemberName] string propertyName = null)
{
if (propertyBackingDictionary.TryGetValue(propertyName, out object value))
{
return (T)value;
}
return default(T);
}
protected bool SetPropertyValue<T>(T newValue, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(newValue, GetPropertyValue<T>(propertyName)))
{
return false;
}
propertyBackingDictionary[propertyName] = newValue;
OnPropertyChanged(propertyName);
Validate();
return true;
}
private ConcurrentDictionary<string, List<string>> errors = new ConcurrentDictionary<string, List<string>>();
public IValidator ModelValidator { get; set; }
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors([CallerMemberName] string propertyName = null)
{
errors.TryGetValue(propertyName, out List<string> errorsForName);
return errorsForName;
}
public bool HasErrors => errors.Count > 0;
public void Validate()
{
errors.Clear();
var validationResults = ModelValidator.Validate(this);
foreach (var item in validationResults.Errors)
{
errors.TryAdd(item.PropertyName, new List<string> { item.ErrorMessage });
OnErrorsChanged(item.PropertyName);
}
}
}
}

How to switch between views in MVVM application

I have a login View after successful login it should open Menu View as it has different tabs.Also I want the tabs to open inside the Menu View itself and the view should be closed once the other view is opened.
I have refered the following links:
Changing the View for a ViewModel
and
switching views in MVVM wpf.
I have done something like this:
MainWindow.xaml
<Window x:Class="JGC_ngCMS_Win.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:JGC_ngCMS_Win.View"
xmlns:VM="clr-namespace:JGC_ngCMS_Win.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<VM:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="View1Template" DataType="{x:Type VM:LoginViewModel}">
<local:LoginView></local:LoginView>
</DataTemplate>
<DataTemplate x:Key="View2Template" DataType="{x:Type VM:MenuViewModel}">
<local:MenuView />
</DataTemplate>
<DataTemplate x:Key="View3Template" DataType="{x:Type VM:UserModuleMapViewModel}">
<local:UserModuleMapView />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentPresenter Content="{Binding ViewModelsView.CurrentItem}" Grid.Row="1"/>
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource View1Template}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SwitchView}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource View2Template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<ContentControl Content="{Binding ViewModel}" />
</Grid>
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
private ViewModelBase _currentViewModel;
readonly static LoginViewModel _loginViewModel = new LoginViewModel();
readonly static MenuViewModel _menuViewModel = new MenuViewModel();
readonly static UserModuleMapViewModel _usermodulemapViewModel = new UserModuleMapViewModel();
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
OnPropertyChanged("CurrentViewModel");
}
}
public ICommand FirstViewCommand { get; private set; }
public ICommand SecondViewCommand { get; private set; }
public MainWindowViewModel()
{
CurrentViewModel = MainWindowViewModel._menuViewModel;
FirstViewCommand = new RelayCommand(() => ExecuteFirstViewCommand());
SecondViewCommand = new RelayCommand(() => ExecuteSecondViewCommand());
//ViewModels = new ObservableCollection<ViewModelBase>()
// {
// new LoginViewModel(),
// new MenuViewModel()
// //new ViewModel3()
// };
//ViewModelsView = CollectionViewSource.GetDefaultView(ViewModels);
}
public void ExecuteFirstViewCommand()
{
CurrentViewModel = MainWindowViewModel._usermodulemapViewModel;
}
private void ExecuteSecondViewCommand()
{
CurrentViewModel = MainWindowViewModel._menuViewModel;
}
My First screen is Login View which is perfect but after successful login Menu View Should open.What mistake am I committing?
Here is one of the possible answers.
I tried to make it as simple as possible, avoiding Styles for example.
The XAML code (I created the LoginViewModel and the MenuViewModel as UserControls to test it)
<Window x:Class="JGC_ngCMS_Win.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:JGC_ngCMS_Win.View"
xmlns:VM="clr-namespace:JGC_ngCMS_Win.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ContentPresenter Content="{Binding CurrentViewModel}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type VM:LoginViewModel}">
<local:LoginView/>
</DataTemplate>
<DataTemplate DataType="{x:Type VM:MenuViewModel}">
<local:MenuView />
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</Window>
Here is the minimum testable code for the ModelView classes
class ViewModelBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
class MainWindowViewModel:ViewModelBase
{
readonly LoginViewModel _loginViewModel = new LoginViewModel();
readonly MenuViewModel _menuViewModel = new MenuViewModel();
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
OnPropertyChanged("CurrentViewModel");
}
}
//Just for test
public void switchView()
{
if (CurrentViewModel == _loginViewModel) { CurrentViewModel = _menuViewModel; }
else { CurrentViewModel = _loginViewModel; }
}
}
class LoginViewModel:ViewModelBase
{
}
class MenuViewModel:ViewModelBase
{
}
Then you just need to specify the DataContext:
ViewModel.MainWindowViewModel mainVM = new ViewModel.MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = mainVM;
}
Remark: I used an explicit instance for the DataContext, but you can work with static properties if you want.

ContextMenu is not setted by DataTrigger, but it is displayed

I use MVVM mode.
There are two ViewModels:
ClientViewModel and DiskViewModel, ClientViewModel has a collection ObservableCollection<DiskViewModel>.
I want to display their hierarchical relationship and do something via their ContextMenu. I set different ContextMenu via TypeToBooleanConverter in DataTrigger in TreeViewItem like this:
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Value="true" Binding="{Binding Converter={cvt:TypeToBooleanConverter},ConverterParameter={x:Type vm:ClientViewModel}}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Client"/>
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
But there is a problem:
If I only set ContextMenu for ClientViewModel via above method, not set ContextMenu for DiskViewModel, it will be show "Client" if right-click on Disk TreeViewItem. But I never set it.
With a picture: this is not I need. I want to nothing if I right-click on Disk TreeViewItem.
Above scene is the simplest. Actually hierarchical relationship is that:
ClientList
|---Client
|---DiskList
|---Disk
|---PartitionList
|---Partition
Different types of item or one type time with different status has different ContextMenu. Enumerate all cases may be a hadr work.
I Need your help.
Code files:
// MainViewModel.cs
using GalaSoft.MvvmLight;
using System.Collections.ObjectModel;
namespace WpfApplication12.ViewModel
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
ClientList = new ObservableCollection<ClientViewModel>();
var disk = new DiskViewModel();
disk.Name = "disk1";
var client = new ClientViewModel();
client.Name = "client1";
client.DiskList.Add(disk);
ClientList.Add(client);
}
#region ClientList
public const string ClientListPropertyName = "ClientList";
private ObservableCollection<ClientViewModel> _clientList;
public ObservableCollection<ClientViewModel> ClientList
{
get
{
return _clientList;
}
set
{
if (_clientList == value)
return;
_clientList = value;
RaisePropertyChanged(ClientListPropertyName);
}
}
#endregion // ClientList
}
public class ClientViewModel : ViewModelBase
{
public ClientViewModel()
{
DiskList = new ObservableCollection<DiskViewModel>();
}
#region Name
public const string NamePropertyName = "Name";
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
return;
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
#endregion // Name
#region DiskList
public const string DiskListPropertyName = "DiskList";
private ObservableCollection<DiskViewModel> _diskList;
public ObservableCollection<DiskViewModel> DiskList
{
get
{
return _diskList;
}
set
{
if (_diskList == value)
return;
_diskList = value;
RaisePropertyChanged(DiskListPropertyName);
}
}
#endregion // DiskList
}
public class DiskViewModel : ViewModelBase
{
public DiskViewModel()
{
}
#region Name
public const string NamePropertyName = "Name";
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
return;
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
#endregion // Name
}
}
.
// MainWindow.xaml
<Window x:Class="WpfApplication12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication12.ViewModel"
xmlns:cvt="clr-namespace:InfoCore.StreamerConsole.Converters"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Source={StaticResource Locator},Path=Main}">
<Grid>
<TreeView Background="AliceBlue" Margin="10"
ItemsSource="{Binding ClientList}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type vm:ClientViewModel}" ItemsSource="{Binding DiskList}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:DiskViewModel}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Value="true" Binding="{Binding Converter={cvt:TypeToBooleanConverter},ConverterParameter={x:Type vm:ClientViewModel}}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Client"/>
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>
<!--<DataTrigger Value="true" Binding="{Binding Converter={cvt:TypeToBooleanConverter},ConverterParameter={x:Type vm:DiskViewModel}}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Disk"/>
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>-->
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Window>
.
// ToBooleanConverter.cs
[MarkupExtensionReturnType(typeof(TypeToBooleanConverter))]
[ValueConversion(typeof(Type), typeof(bool))]
public class TypeToBooleanConverter : MarkupExtension, IValueConverter
{
private static TypeToBooleanConverter _converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _converter ?? (_converter = new TypeToBooleanConverter());
}
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (value == null)
return Binding.DoNothing;
return Type.Equals(value.GetType(), parameter);
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}

How to Display Dynamic Menu in WPF application

I am new to WPF. I try to create a dynamic menus in WPF. I plan to use data from a database. I am using WPF Application in vs 2010.
Until now what I done was:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="850" Width="725" WindowStartupLocation="CenterScreen" WindowState="Maximized" >
<Menu>
<Menu.Resources>
<Style x:Key="ThemeMenuItemStyle" TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"></Setter>
<Setter Property="Command" Value="{Binding ActivateCommand}"/>
<Setter Property="IsChecked" Value="{Binding IsActive}" />
<Setter Property="IsCheckable" Value="True"/>
</Style>
</Menu.Resources>
<MenuItem Header="Themes" ItemsSource="{Binding Themes}"
ItemContainerStyle="{StaticResource ThemeMenuItemStyle}" />
</Menu>
</Window>
But I am stuck in code behind to how assign the value to menu.
I need the full page code to achieve this. I plan to use list as datasource.
this works for me (simple example for "dynamic" menu)
view:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="300">
<Window.Resources>
<DataTemplate DataType="local:MyData2">
<MenuItem Header="{Binding Name}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Menu>
<Menu.Resources>
<Style x:Key="ThemeMenuItemStyle" TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"></Setter>
<Setter Property="ItemsSource" Value="{Binding More}"></Setter>
</Style>
</Menu.Resources>
<MenuItem Header="Themes" ItemsSource="{Binding Themes}"
ItemContainerStyle="{StaticResource ThemeMenuItemStyle}" />
</Menu>
</Grid>
code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyThemesViewModel();
}
public class MyThemesViewModel
{
ObservableCollection<MyData> _themes = new ObservableCollection<MyData>();
public ObservableCollection<MyData> Themes
{
get { return _themes; }
set { _themes = value; }
}
public MyThemesViewModel()
{
Themes.Add(new MyData("a"));
Themes.Add(new MyData("b"));
Themes.Add(new MyData("c"));
Themes.Add(new MyData("d"));
}
}
}
public class MyData
{
ObservableCollection<MyData2> _more = new ObservableCollection<MyData2>(){new MyData2("h")};
public ObservableCollection<MyData2> More {get { return _more; } }
public MyData(string name)
{
Name = name;
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
public class MyData2
{
public MyData2(string name)
{
Name = name;
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
public class MenuService
{
private List<MenuItem> allMenuItems;
public MenuService()
{
allMenuItems = new List<MenuItem>();
}
public List<MenuItem> GetParentMenuItems()
{
List<MenuItem> parentItems = allMenuItems.FindAll(x => x.Parent == null);
return parentItems;
}
public void AddMenuItem(MenuItem item, string parentName = "")
{
if (parentName == string.Empty)
{
this.allMenuItems.Add(item);
}
else
{
MenuItem parent = allMenuItems.Find(x => x.Name == parentName);
if (parent != null)
{
item.Parent = parent;
parent.AddSubMenu(item);
}
allMenuItems.Add(item);
}
}
public void RemoveMenuItem(MenuItem menuItem)
{
foreach (MenuItem item in allMenuItems)
{
item.RemoveSubMenu(menuItem);
}
if (this.allMenuItems.Contains(menuItem))
{
this.allMenuItems.Remove(menuItem);
}
}
}
Thanks,
Jeff Jones
Lead at Mobile Application Development Company

Databinding to Command in Silverlight Templated Button control

I am trying to create a templated button control with databinding for the Visibility, tooltip, and Command. The Visibility binding works, as does the tooltip, but the Command does not. Another process is responsible for injecting the viewmodel and associating it with the View, and the other data bindings are working so I am pretty confident that is working properly.
In the resource dictionary:
<Converters:BoolToVisibilityConverter x:Key="boolVisibilityConverter" />
<Style TargetType="local:ImageButton">
<Setter Property="Visibility" Value="{Binding FallbackValue=Visible, Path=ToolIsAvailable, Converter={StaticResource boolVisibilityConverter} }"/>
<Setter Property="Command" Value="{Binding ButtonCommand}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ImageButton">
<Grid>
<Image Source="{TemplateBinding Image}"
ToolTipService.ToolTip="{Binding ToolName}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
the templated control
public class MyButton: ImageButton
{
public MyButton(MyCommandViewModel viewmodel)
{
this.DefaultStyleKey = typeof(ImageButton);
this.Image = new BitmapImage(new Uri("/MyProject;component/Themes/myimage.png", UriKind.Relative));
this.DataContext = viewmodel;
}
}
and in the view model
public MyCommandViewModel()
: base("My Tool", true)
{
}
public class CommandViewModel
{
public CommandViewModel(string toolName, bool isAvailable)
{
ToolIsAvailable = isAvailable;
ToolName = toolName;
_buttoncommand = new DelegateCommand(() =>
{
ExecuteCommand();
},
() => { return CanExecute; });
}
private bool _canExecute = true;
public bool CanExecute
{
get { return _canExecute; }
set
{
_canExecute = value;
OnPropertyChanged("CanExecute");
if (_command != null) _command.RaiseCanExecuteChanged();
}
}
private DelegateCommand _buttoncommand;
public ICommand ButtonCommand
{
get { return _buttoncommand; }
}
protected virtual void ExecuteCommand()
{
}
public bool ToolIsAvailable
{
get { return _toolIsReady; }
set { _toolIsReady = value; OnPropertyChanged("ToolIsAvailable"); }
}
public string ToolName
{
get { return _toolName; }
set { _toolName = value; OnPropertyChanged("ToolName"); }
}
}
Why are the other databindings functioning properly but not the Command data binding. I found this similar post
Overriding a templated Button's Command in WPF
Do I need to template a grid control instead and use RoutedCommands? I am not sure I understand why Silverlight treats the Command binding different than the others so I suspect I just have a bug in the code.
Does specifically looking for the datacontext work?
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ButtonCommand}"
This was my solution. Using the same commandviewmodel as above and same MyCommandViewModel
<Style TargetType="local:ImageButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ImageButton">
<Grid>
<Image Source="{TemplateBinding Image}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The databinding is now done in a user control
<UserControl x:Class="SilverlightApplication11.Test"
...
>
<UserControl.Resources>
<Converters:BoolToVisibilityConverter x:Key="boolVisibilityConverter" />
</UserControl.Resources>
<Grid>
<local:ImageButton Image="/SilverlightApplication11;component/Themes/hand.png" Command="{Binding ButtonCommand}" Visibility="{Binding FallbackValue=Visible, Path=ToolIsAvailable, Converter={StaticResource boolVisibilityConverter} }"/>
</Grid>
</UserControl>
and the code behind
public Test(TestCommandViewModel vm)
{
InitializeComponent();
this.Loaded += (o, e) => this.DataContext = vm;
}

Resources