How to switch between views in MVVM application - wpf

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.

Related

WPF binding of property with multiple usercontrols level

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

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);
}
}
}
}

WPF ComboBoxAdaptor does not display the ComboBox property

https://stackoverflow.com/a/36192552/9387175
In this answer the user suggests that the comboBoxAdaptor can be used to add an item to a combo box even if it does not exist in the item source. I do in fact see that it is working in the code, but I can't figure out why it refuses to display. The normal combo box functions correctly in the below example, the comboBoxAdaptor is not visible. Am I missing something like styles or templates? I can't seem to find the right combination.
My xaml:
<Window x:Class="WpfApp1.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:adapters="clr-namespace:WpfApp1.Adapters"
mc:Ignorable="d"
Title="MainWindow"
Height="200"
Width="650">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="210" />
<ColumnDefinition Width="210" />
</Grid.ColumnDefinitions>
<adapters:ComboBoxAdaptor Grid.Column="0"
AllowNull="False"
Height="80"
ItemsSource="{Binding Path=DataEntries}"
SelectedItem="{Binding Path=DataEntry}">
<ComboBox Height="80" />
</adapters:ComboBoxAdaptor>
<ComboBox Grid.Column="1"
Height="80"
ItemsSource="{Binding Path=DataEntries}"
SelectedItem="{Binding Path=DataEntry}"
DisplayMemberPath="Name"
SelectedValuePath="Name" />
</Grid>
</Window>
My Code:
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SampleViewModel vm = new SampleViewModel();
DataContext = vm;
}
}
public class SampleDataClass
{
public string Name { get; set; }
public SampleDataClass(string name)
{
Name = name;
}
}
public class SampleViewModel : INotifyPropertyChanged
{
private readonly IList<SampleDataClass> _dataEntries;
private string _dataEntry;
public event PropertyChangedEventHandler PropertyChanged;
public SampleViewModel()
{
IList<SampleDataClass> list = new List<SampleDataClass>();
list.Add(new SampleDataClass("tools"));
list.Add(new SampleDataClass("set"));
list.Add(new SampleDataClass("sort"));
list.Add(new SampleDataClass("flap"));
_dataEntries = list;
}
public IList<SampleDataClass> DataEntries
{
get { return _dataEntries; }
}
public string DataEntry
{
get
{
return _dataEntry;
}
set
{
if (_dataEntry == value) {return;}
_dataEntry = value;
OnPropertyChanged("DataEntry");
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Turns out that I was missing the style that links the ComboBox to the content of the ContentControl (ComboBoxAdaptor)
Style Example
<Style TargetType="adapters:ComboBoxAdaptor">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="adapters:ComboBoxAdaptor">
<ContentPresenter Content="{TemplateBinding ComboBox}"
Margin="{TemplateBinding Padding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Binding visibility to bool value in WPF dataGrid

I am aware that as DataGrid columns aren't part of the Visual Tree, you can't bind the visibility property of the columns directly to a boolean property in your VM. You have to do it another way. Below is the way I have done it:
public class LocalVm
{
public static ObservableCollection<Item> Items
{
get
{
return new ObservableCollection<Item>
{
new Item{Name="Test1", ShortDescription = "Short1"}
};
}
}
public static bool IsDetailsModeEnabled
{
get { return true; }
}
}
public class Item : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
private string _shortDescription;
public string ShortDescription
{
get
{
return _shortDescription;
}
set
{
_shortDescription = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Window x:Class="Wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wpf"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DataContext="{d:DesignInstance Type=local:LocalVm}"
Title="MainWindow" Height="350" Width="525"
Name="MyWindow">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<DataGridTextColumn x:Key="ThatPeskyColumn"
Binding="{Binding Size}"
Visibility="{Binding DataContext.IsDetailsModeEnabled,
Source={x:Reference MyWindow},
Converter={StaticResource BoolToVis}}"/>
</Window.Resources>
<Grid>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" />
<StaticResource ResourceKey="ThatPeskyColumn"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
However, in my xaml window, there is an error on the "visibility" property: "Object reference not set to an instance of an object". If I remove the source and converter part, the error goes but it doesn't bind properly. What I am doing wrong?
thanks in advance
As I can see from the code you've provided, IsDetailsModeEnabled property is static. To bind to static property in that case, you may just create your VM as a static resource, bind to its static property and set it to Window DataContext later:
<Window.Resources>
<local:LocalVm x:Key="vm" />
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<DataGridTextColumn x:Key="ThatPeskyColumn"
Binding="{Binding ShortDescription}"
Visibility="{Binding Path = IsDetailsModeEnabled,
Source={StaticResource vm},
Converter={StaticResource BoolToVis}}"/>
</Window.Resources>
<Window.DataContext>
<StaticResource ResourceKey="vm"/>
</Window.DataContext>
From the other hand, more classical approach in that case would be with a proxy Freezable object, described here:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
XAML
<Window.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<DataGridTextColumn x:Key="ThatPeskyColumn"
Binding="{Binding ShortDescription}"
Visibility="{Binding Path=Data.IsDetailsModeEnabled,
Source={StaticResource proxy},
Converter={StaticResource BoolToVis}}"/>
</Window.Resources>

Cant access nested wpf item command by mvvm

Lets say i got a dummy WPF application (MVVM oriented).
My main window contains a custom List i created, and the list contains a custom item.
the item has an image button, and i want the button command to be the command i got in the viewmodel. the viewmodel is binded to the main window.
how can i do it ?
i attached the dummy project (download it here : http://www.2shared.com/file/qmO3E5rx/NestedCommand.html
or here : http://www.multiupload.nl/KCFLSKAIH0),
but if you don't want to download it,
the code goes like this :
MainWindow XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Application="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Application:List x:Name="myList" DataContext="{Binding}" />
</Grid>
MainWindow Code-Behind:
public MainWindow()
{
InitializeComponent();
CharacterViewModel viewModel = new CharacterViewModel();
this.myList.ItemsList.ItemsSource = viewModel.Model.Powers;
}
List XAML:
<UserControl x:Class="WpfApplication2.List"
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:Application="clr-namespace:WpfApplication2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ListView x:Name="ItemsList" ItemsSource="{Binding Path=Name}">
<ListView.ItemTemplate>
<DataTemplate>
<Application:Item x:Name="myItem" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Item XAML:
<UserControl x:Class="WpfApplication2.Item"
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="50" d:DesignWidth="50">
<Grid>
<Button x:Name="ButtonImage" Command="**????????**">
<Button.Template>
<ControlTemplate>
<Border HorizontalAlignment="Center" VerticalAlignment="Center" >
<Image Width="50" Height="50" Source="/WpfApplication2;component/Images/Jellyfish.jpg"/>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
ViewModel Code :
public class CharacterViewModel : ObjectBase
{
public Character Model { get; private set; }
public DelegateCommand<object> RemoveCommand { get; private set; }
public CharacterViewModel()
: this(Character.Create())
{
}
public CharacterViewModel(Character model)
{
Model = model;
RemoveCommand = new DelegateCommand<object>(RemoveCommand_Execute, RemoveCommand_CanExecute, "Save");
}
void RemoveCommand_Execute(object arg)
{
Model.Powers.Clear();
MessageBox.Show(string.Format("{0} character powers removed.", Model.Name));
}
bool RemoveCommand_CanExecute(object arg)
{
return Model.Name != string.Empty;
}
}
Model Code:
public class Character : ObjectBase
{
string _Name = string.Empty;
ObservableCollection<string> _Powers = new ObservableCollection<string>();
public string Name
{
get { return _Name; }
set
{
if (_Name == value)
return;
_Name = value;
OnPropertyChanged("Name");
}
}
public ObservableCollection<string> Powers
{
get { return _Powers; }
}
public static Character Create()
{
Character hero = new Character()
{
Name = "Superman",
};
hero.Powers.Add("Flight");
hero.Powers.Add("Strength");
hero.Powers.Add("X-Ray Vision");
return hero;
}
}
Framework Code :
public class DelegateCommand<T> : ICommand
{
public DelegateCommand(Action<T> execute) : this(execute, null) { }
public DelegateCommand(Action<T> execute, Predicate<T> canExecute) : this(execute, canExecute, "") { }
public DelegateCommand(Action<T> execute, Predicate<T> canExecute, string label)
{
_Execute = execute;
_CanExecute = canExecute;
Label = label;
}
readonly Action<T> _Execute = null;
readonly Predicate<T> _CanExecute = null;
public string Label { get; set; }
public void Execute(object parameter)
{
_Execute((T)parameter);
}
public bool CanExecute(object parameter)
{
return _CanExecute == null ? true : _CanExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
if (_CanExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_CanExecute != null)
CommandManager.RequerySuggested -= value;
}
}
}
public abstract class ObjectBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected internal void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Thanks for helping
The DataContext for the ListItem is the item that it is bound to which is not what you're looking for. You're looking for the DataContext of the UserControl and to get that you'll need to either reference the UserControl explicitly using ElementName or use a RelativeSource binding to explore the visual tree. RelativeSource is probably the best solution and since it references the control itself you'll need to specify in the Path of the binding that you're looking for the RemoveCommand member on the DataContext - something like Path=DataContext.RemoveCommand. See full example below.
XAML:
<Grid DataContext="{Binding}"> <!-- Set the binding for the DataContext of the control and all of its children -->
<ListView ItemsSource="{Binding Path=Model.Powers}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- Use RelativeSource to access the Grid control and then get its DataContext -->
<Button Command="{Binding Path=DataContext.RemoveCommand, RelativeSource={RelativeSource AncestorType=Grid}}">
<Border HorizontalAlignment="Center" VerticalAlignment="Center" >
<Image Width="50" Height="50" Source="/WpfApplication2;component/Images/Jellyfish.jpg"/>
</Border>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>

Resources