WPF two way mode binding using INotifyPropertyChanged not working - c#

So I am facing a tricky issue. This is my view model:
namespace Market.ViewModel
{
public class BillingViewModel : ViewModelBase
{
#region Private Members
private Customer _customer;
private Product _products;
private string _productId = "asd";
RelayCommand _numClickedCommand;
#endregion
public Customer Customer
{
get { return _customer; }
set
{
_customer = value;
NotifyPropertyChanged("Customer");
}
}
public Product Products
{
get { return _products; }
set
{
_products = value;
NotifyPropertyChanged("Products");
}
}
public bool CanClick
{
get { return true; }
}
public string ProductId
{
get { return _productId; }
set
{
_productId = value;
NotifyPropertyChanged("ProductId");
}
}
public ICommand NumClickedCommand
{
get
{
if (_numClickedCommand == null)
{
_numClickedCommand = new RelayCommand(param => this.NumClicked(param.ToString()),
param => this.CanClick);
}
return _numClickedCommand;
}
}
#region PrivateMethods
private void NumClicked(string numClicked)
{
ProductId = ProductId+numClicked;
}
#endregion
}
}
It inherits ViewModelBase which implements INotifyPropertyCanged.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And my view is:
<Window x:Class="Billing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:controls="clr-namespace:Billing"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:Market.ViewModel;assembly=Market.ViewModel"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewModel:BillingViewModel x:Key="ViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource ViewModel}}">
<controls:NumPad HorizontalAlignment="Left" Margin="265,205,0,-192" VerticalAlignment="Top" Height="307" Width="242"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="247,29,0,0" TextWrapping="Wrap" Text="{Binding ProductId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
NumClickedCommand is used in this xaml:
<UserControl x:Class="Billing.NumPad"
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:viewModel="clr-namespace:Market.ViewModel;assembly=Market.ViewModel"
mc:Ignorable="d" Height="109" Width="248">
<UserControl.Resources>
<viewModel:BillingViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid Height="109" VerticalAlignment="Top" DataContext="{Binding Source={StaticResource ViewModel}}">
<Button Content="1" HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Top" Width="75" CommandParameter="1" Command="{Binding NumClickedCommand}"/>
<Button Content="2" HorizontalAlignment="Left" Margin="90,0,0,0" VerticalAlignment="Top" Width="75" CommandParameter="2" Command="{Binding NumClickedCommand}"/>
<Button Content="3" HorizontalAlignment="Left" Margin="170,0,0,0" VerticalAlignment="Top" Width="75" CommandParameter="3" Command="{Binding NumClickedCommand}"/>
<Button Content="4" HorizontalAlignment="Left" Margin="10,27,0,0" VerticalAlignment="Top" Width="75" CommandParameter="4" Command="{Binding NumClickedCommand}"/>
<Button Content="5" HorizontalAlignment="Left" Margin="90,27,0,0" VerticalAlignment="Top" Width="75" CommandParameter="5" Command="{Binding NumClickedCommand}"/>
<Button Content="6" HorizontalAlignment="Left" Margin="170,27,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="1.034,2.171" CommandParameter="6" Command="{Binding NumClickedCommand}"/>
<Button Content="7" HorizontalAlignment="Left" Margin="10,54,0,0" VerticalAlignment="Top" Width="75" CommandParameter="7" Command="{Binding NumClickedCommand}"/>
<Button Content="8" HorizontalAlignment="Left" Margin="90,54,0,0" VerticalAlignment="Top" Width="75" CommandParameter="8" Command="{Binding NumClickedCommand}"/>
<Button Content="9" HorizontalAlignment="Left" Margin="170,54,0,0" VerticalAlignment="Top" Width="75" CommandParameter="9" Command="{Binding NumClickedCommand}"/>
<Button Content="0" HorizontalAlignment="Left" Margin="10,81,0,0" VerticalAlignment="Top" Width="75" CommandParameter="0" Command="{Binding NumClickedCommand}"/>
<Button Content="Submit" HorizontalAlignment="Left" Margin="90,81,0,0" VerticalAlignment="Top" Width="155" />
</Grid>
</UserControl>
The problem is that updaditing ProductId in the viewmodel doesnt reflect in the view. The initial value of asd is updated at the launch of the application. The controls contain set of buttons that implements ICommand interface and it all calls the NumClicked() in viewmodel. While debugging if i click the button NumClicked() is called then ProductId is updated and NotifyPropertyChanged() is also called but UI does not update, it remains the same. But in case i directly update the UI i.e i enter some value in the textbox the same flow happens, PropertyChanged() is called and after that get is also called to update the value in the viewmodel.
I have gone through many such questions already available but not able to get what exactly is stopping the update of the UI. Any help is appreciated and do ask if anything is missing.
Thank You.

DataContext of Grid and TextBox are bound to a view model instance from Window resources.
NumPad control declares its own instance of view model
NumClickedCommand works with wrong data, not with the displayed object
make sure you have only one instance of view model
NumPad inherits DataContext and shouldn't create a new object and change DataContext
<UserControl x:Class="Billing.NumPad"
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:viewModel="clr-namespace:Market.ViewModel;assembly=Market.ViewModel"
mc:Ignorable="d" Height="109" Width="248">
<Grid Height="109" VerticalAlignment="Top">
<!--all Buttons here-->
</Grid>

you are passing same viewmodel into numpadUser controll and mainWindow also as DataContext, so that will create new instance of viewmodel,so according to me you can use mainwindow's DataContext in NumPad also using Minwindow's GridName, you have to name your grid like
<Grid DataContext="{Binding Source={StaticResource ViewModel}}" x:Name="grdNumPad">
and you can access that DataContext in your NumPad.XAML this way
<Button Content="1" HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Top" Width="75" CommandParameter="1" Command="{Binding DataContext.NumClickedCommand,ElementName=grdNumPad}"/>

Related

How can I use DelegateCommand correctly?

In the debug mode, goes from constructor to the set method, but in the get method does not enter at any point, which I think is the problem, but I don't know how to solved.
In the .xaml file I defined the button.
<dx:ThemedWindow
x:Class="ProductsModule.View.ProductView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
Title="MenuView" Height="800" Width="1000">
<Grid Margin="20" VerticalAlignment="Top" Background="White" Height="420" DataContext="{Binding Detail}">
<Grid.Effect>
<DropShadowEffect BlurRadius="20" ShadowDepth="1"/>
</Grid.Effect>
<StackPanel Margin="750 70 70 70" HorizontalAlignment="Left">
<TextBlock Text="{Binding Name}" FontSize="18" Margin="0 5" Foreground="#FF6A6A6A"/>
<Button Command="{Binding ShowCommand }">Add data</Button>
</StackPanel>
</Grid>
</dx:ThemedWindow>
In .xaml.cs file I defined datacontext.
public SecondView()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
VIEW MODEL
public ViewModel()
{
ShowCommand = new DelegateCommand(ShowMethod);
}
public ICommand ShowCommand
{
get;
set;
}
private void ShowMethod()
{
OrderView orderView = new OrderView();
orderView.Show();
}
When I press the button, ShowMethod() is not called and does not do anything.

WPF - Observablecollection binded DataGrid not updating when i add item from other window

Its my first time with WPF. I already seen many questions but couldn't get the right answer. Scenario is that I have ObservableCollection of order which is implemented by ViewModel. Here is the code
namespace WpfApplication2.ViewModels
{
public class ViewModel : ObservableCollection<Order>
{
static ObservableCollection<Order> orderlist = new ObservableCollection<Order>();
public ParameterCommand ParameterCommand { get; set; }
public ViewModel()
{
this.ParameterCommand = new ParameterCommand(this);
for (int i = 0; i < 10; i++)
{
Order o = new Order();
o.Imei = "Person"+ i;
o.price = i*20;
o.Cust_ID = i;
Add(o);
}
}
public void ParameterMethod(Order order)
{
Order o = new Order();
o.Imei = order.Imei;
o.price = order.price;
o.Cust_ID = order.Cust_ID;
Add(o);
}
}
}
Now I have the Main Window which have the Datagrid binded by above viewmodel
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:view ="clr-namespace:WpfApplication2"
xmlns:model="clr-namespace:WpfApplication2.ViewModels"
Title="MainWindow" Height="359.388" Width="630.791">
<Window.Resources>
<view:Order x:Key="order"></view:Order>
<model:ViewModel x:Key="modelview"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource modelview}}" >
<DataGrid HorizontalAlignment="Left" AutoGenerateColumns="False" Margin="48,26,0,0" DataContext="{Binding Source={StaticResource modelview}}" ItemsSource="{Binding}" VerticalAlignment="Top" Height="235" Width="373">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Imei,Mode=TwoWay}"/>
<DataGridTextColumn Header="Cutomer ID" Binding="{Binding Cust_ID,Mode=TwoWay}"/>
<DataGridTextColumn Header="Price" Binding="{Binding price,Mode=TwoWay}"/>
</DataGrid.Columns>
</DataGrid>
<TextBox HorizontalAlignment="Left" Height="23" DataContext="{Binding Source={StaticResource order}}" Margin="490,69,0,0" Text="{Binding Path=Imei, Mode=TwoWay}" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" TextChanged="TextBox_TextChanged_1"/>
<TextBox HorizontalAlignment="Left" Height="23" DataContext="{Binding Source={StaticResource order}}" Margin="490,119,0,0" Text="{Binding Path=Cust_ID, Mode=TwoWay}" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Height="23" DataContext="{Binding Source={StaticResource order}}" Margin="490,165,0,0" Text="{Binding Path=price, Mode=TwoWay}" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<Button Content="NEW" HorizontalAlignment="Left" Margin="109,276,0,0" VerticalAlignment="Top" Width="99" Command="{Binding ParameterCommand, Source={StaticResource modelview}}" CommandParameter="{Binding Source={StaticResource order}}" Height="25"/>
<Button Content="NEW" HorizontalAlignment="Left" Margin="239,276,0,0" VerticalAlignment="Top" Width="99" Height="25" Click="Button_Click_1"/>
</Grid>
now when i add the values from main window text boxes to ObservableCollection it is updated in the datagrid but when i use other separate window for adding item in ObservableCollection it got added in it but datagrid is not updated. Can you please help me out
EDIT:
here is the xaml of the window I am adding item to ObservableCollection
Title="New" Height="300" Width="300">
<Window.Resources>
<view:Order x:Key="order"></view:Order>
<model:ViewModel x:Key="modelview"/>
</Window.Resources>
<Grid>
<TextBox HorizontalAlignment="Left" Text="{Binding Path=Imei, Mode=TwoWay}" Height="23" Margin="83,30,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Text="{Binding Path=Cust_ID, Mode=TwoWay}" Height="23" Margin="83,85,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Text="{Binding Path=price, Mode=TwoWay}" Height="23" Margin="83,136,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<Button Content="Save to grid" HorizontalAlignment="Left" Margin="104,196,0,0" VerticalAlignment="Top" Width="75" Command="{Binding ParameterCommand, Source={StaticResource modelview}}" CommandParameter="{Binding Source={StaticResource order}}"/>
</Grid>
Commands also attaching in case any problem is there
namespace WpfApplication2.ViewModels.Commands
{
public class ParameterCommand : ICommand
{
public ViewModel ViewMod
{
get ;
set ;
}
public ParameterCommand(ViewModel view)
{
this.ViewMod = view;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
this.ViewMod.ParameterMethod(parameter as Order);
}
}
}
ADDED
Main Window.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//ViewModel vm = new ViewModel();
//this.DataContext = vm;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
New win = new New();
win .Owner = this;
win .DataContext = this.DataContext;
win .Show();
}
}
It's cause you are creating new object of ViewModel in new window and using data as DataContext. try below code:
Window w = new Window();
w.DataContext = this.DataContext;
w.Show();
When you open the new window, use current DataContext instance as new window's DataContext. And your code will work fine.
Update
Remove below part from you New Window.
<Window.Resources>
<view:Order x:Key="order"></view:Order>
<model:ViewModel x:Key="modelview"/>
</Window.Resources>
Cause of <model:ViewModel x:Key="modelview"/> a new object of ViewModel is being created with the New Window.
Change your Command as following:
<Button Content="Save to grid" HorizontalAlignment="Left" Margin="104,196,0,0" VerticalAlignment="Top" Width="75" Command="{Binding ParameterCommand}" CommandParameter="{Binding Source={StaticResource order}}"/>
Not sure what are you trying to do in CommandParameter. But your New
Window is getting existing ViewModel object as DataContext.
Command can be directly bound to Button.

Prism 6 Navigation Wierdness

I am still learning prism and I want to understand navigation. Having read the manual and followed #brianlagunas MVVM made simple webinar online, I decided to try a small project and see. I get a stackoverflow exception when I try to navigate to a view that has a viewmodel data-bound to some controls on my view. Here is the code below:
Profile View with empty viewmodel:
<UserControl x:Class="SampleLogin.Views.Profile"
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:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:local="clr-namespace:SampleLogin.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock x:Name="ProfileTextBlock" HorizontalAlignment="Center" Margin="74,29,78,0" TextWrapping="Wrap" Text="PROFILE PAGE" VerticalAlignment="Top" Height="45" Width="148" FontSize="21.333" FontWeight="Bold"/>
<TextBlock x:Name="Username" HorizontalAlignment="Left" Height="26" Margin="103,96,0,0" TextWrapping="Wrap" Text="{Binding username}" VerticalAlignment="Top" Width="130"/>
<TextBlock x:Name="LoginTime" HorizontalAlignment="Left" Margin="109,152,0,0" TextWrapping="Wrap" Text="{Binding LoginTime}" VerticalAlignment="Top" Width="124" Height="33"/>
<Label x:Name="label" Content="Username :" HorizontalAlignment="Left" Margin="10,96,0,0" VerticalAlignment="Top" Width="71" FontWeight="Bold"/>
<Label x:Name="label1" Content="Login-Time:" HorizontalAlignment="Left" Height="26" Margin="12,159,0,0" VerticalAlignment="Top" Width="86" FontWeight="Bold"/>
</Grid>
but it has an empty viewmodel and it loads fine.
Here is another view with a simple form that is data-bound to its viewmodel
<UserControl x:Class="SampleLogin.Views.LoginUser"
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:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:local="clr-namespace:SampleLogin.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="325">
<Grid>
<Label x:Name="usernameLabel" Content="Username :" HorizontalAlignment="Left" Margin="10,93,0,0" VerticalAlignment="Top" Width="113" Height="35" FontSize="18.667" FontWeight="Bold"/>
<Label x:Name="label" Content="Password :
" HorizontalAlignment="Left" Margin="10,146,0,0" VerticalAlignment="Top" Width="100" Height="38" FontSize="18.667" FontWeight="Bold"/>
<TextBox x:Name="Username" HorizontalAlignment="Left" Height="35" Margin="132,93,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="178" Text="{Binding Username, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<PasswordBox x:Name="passwordBox" HorizontalAlignment="Left" Margin="132,146,0,0" VerticalAlignment="Top" Width="178" Height="35"/>
<Button x:Name="Loginbtn" Content="Login" HorizontalAlignment="Left" Margin="85,208,0,0" VerticalAlignment="Top" Width="89" Height="42" FontSize="18.667" FontWeight="Bold"
Command="{Binding LoginCommand}" CommandParameter="{Binding ElementName=passwordBox}" />
<Button x:Name="Logoutbtn" Content="Logout" HorizontalAlignment="Left" Margin="230,208,0,0" VerticalAlignment="Top" Width="80" Height="42" FontWeight="Bold" FontSize="18.667"/>
</Grid>
Here is the viewmodel its bound to: left it very simple just to understand the concept:
public class LoginUserViewModel : BindableBase
{
private AuthenticationService _auth;
public DelegateCommand<object> LoginCommand { get; set; }
public LoginUserViewModel(AuthenticationService auth)
{
_auth = auth;
LoginCommand = new DelegateCommand<object>(LoginUser, CanLoginUser);
}
private void LoginUser(object obj)
{
var passwdbox = obj as PasswordBox;
_auth.LoginUser(Username, passwdbox.SecurePassword);
}
private bool CanLoginUser(object obj)
{
var password = obj as PasswordBox;
return string.IsNullOrWhiteSpace(Username);
}
private string _username = "Enter Username here";
public string Username
{
get { return _username; }
set { SetProperty(ref _username, value); }
}
}
And here is the MainWindowViewmodel that handles the navigation using the button at the top of my mainwindow view.
public class MainWindowViewModel : BindableBase
{
IRegionManager _regions;
public DelegateCommand<string> NavigationCommand { get; set; }
public MainWindowViewModel(IRegionManager regions)
{
_regions = regions;
NavigationCommand = new DelegateCommand<string>(Navigate);
}
private void Navigate(string uri)
{
_regions.RequestNavigate("ContentRegion", uri);
}
}
And lastly, here is the MainWindowViewModel that binds to the viewmodel:
<Window x:Class="SampleLogin.Views.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:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:local="clr-namespace:SampleLogin.Views"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="Login" Margin="5" Command="{Binding NavigationCommand, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" CommandParameter="LoginUser" />
<Button Content="Home" Margin="5" Command="{Binding NavigationCommand, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" CommandParameter="Home"/>
<Button Content="Profile" Margin="5" Command="{Binding NavigationCommand}" CommandParameter="Profile"/>
</StackPanel>
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" />
</Grid>
I find this very strange. The first view works when I click the buttons at the top of the screen for navigation as long as I leave the ViewModel blank. Same with the view that has the form, if I comment out the ViewModel, it navigates with no issues, showing the form and all. But if the databinding is there in the viewmodel I get the exception. Any ideas?! I am stomped and not sure where my mistake is.
Edit:
Having thought about this problem and tried to move it forward, I discovered that the error comes from the fact that my LoginUserViewModel having constructor parameters is why I am having the errors, tried using unity container in the bootstrapper register LoginUserViewmodel's dependencies but I am still getting the errors, any ideas anyone?
After doing some more searching and question asking, I got the answer to this problem. I spoke to the awesome #brianLagunas and shared my code with him and he made understand the problem I had. Its in a domain model which I didn't include in my post.
public class User : IUser
{
public User()
{
}
public User(User user){} //this is the problem line
[Required]
[Key]
public int UserId { get; set; }
[Required(ErrorMessage ="You must supply a Username")]
[DataType(DataType.Text)]
public string Username { get; private set; }
[Required(ErrorMessage = "You must enter a valid password to Login!")]
public SecureString Password { get; private set; }
[DataType(DataType.Time)]
[Required]
public TimeSpan TimeLoggedIn { get; private set; }
[DataType(DataType.Date)]
public DateTime LoginHistory { get; private set; }
public void SetUserDetails(string username, TimeSpan date, DateTime History)
{
if(string.IsNullOrWhiteSpace(username))
{
throw new ArgumentNullException("You cannot have any of the Login Fields empty..!");
}
Username = username;
TimeLoggedIn = date;
LoginHistory = History;
}
}
The second constructor was casing an infinite loop this the stack overflow error I was getting. Removing that constructor fixed the issue and all is well.

Navigating Views in WPF

I am new to WPF, just got thrown on a project. There is already an existing LoginWindow, and currently there is a forgot password button which opens a new dialog window. The manager wants that changed to use the same window, instead of opening a new one on top of it. I haven't seen a really solid example of how to accomplish this, as there seems to be a lot of different ways to do things in WPF. My initial though was maybe to use a Panel like a div and show and hide them in the same window like I DIV in HTML but not sure.
The current login someone already built looks like this:
<Controls:MetroWindow x:Class="Omega.LoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:Omega.Properties"
xmlns:tools="clr-namespace:Omega.modules.Features"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
Title="USER LOGIN" Icon="resources/icons/OmegaApp.ico"
TextOptions.TextFormattingMode="Display"
ShowInTaskbar="True" Topmost="False"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
GlowBrush="{DynamicResource AccentColorBrush}" BorderThickness="1" EnableDWMDropShadow="True" WindowTransitionsEnabled="False" Closed="LoginWindow_Close" Height="446" Width="808" Visibility="Visible">
<Controls:MetroWindow.Background>
<ImageBrush ImageSource="resources\images\Omega-BGScreen.jpg"/>
</Controls:MetroWindow.Background>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/resources/Resource_Dictionary.xaml"/>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/FlatButton.xaml" />
<ResourceDictionary Source="/resources/Icons.xaml" />
</ResourceDictionary.MergedDictionaries>
<x:Array x:Key="WindowCommandsOverlayBehaviorArray"
Type="Controls:WindowCommandsOverlayBehavior">
<Controls:WindowCommandsOverlayBehavior>Always</Controls:WindowCommandsOverlayBehavior>
<Controls:WindowCommandsOverlayBehavior>Flyouts</Controls:WindowCommandsOverlayBehavior>
<Controls:WindowCommandsOverlayBehavior>HiddenTitleBar</Controls:WindowCommandsOverlayBehavior>
<Controls:WindowCommandsOverlayBehavior>Never</Controls:WindowCommandsOverlayBehavior>
</x:Array>
</ResourceDictionary>
</Window.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Grid.ColumnSpan="3" Source="resources/images/Omega_Logo.png" HorizontalAlignment="Left" Height="100" Margin="58,48,0,0" VerticalAlignment="Top" Width="186" Grid.RowSpan="2"/>
<TextBox Name="UserID_Text" Grid.ColumnSpan="2" Grid.Column="2" HorizontalAlignment="Left" Height="30" Grid.Row="2" TextWrapping="Wrap" Text="Omega" VerticalAlignment="Top" Width="190" VerticalContentAlignment="Center" Margin="0,30,0,0" TabIndex="0" IsTabStop="True"/>
<Button Style="{StaticResource StandardButton}" Content="{x:Static properties:Resources.Login_Label}" IsDefault="True" HorizontalAlignment="Left" Grid.Row="3" VerticalAlignment="Top" Width="400" Grid.ColumnSpan="4" Height="30" Grid.Column="2" Click="Login_Button_Click" TabIndex="2" />
<TextBox HorizontalAlignment="Left" Height="23" Margin="0,33,0,0" Grid.Row="5" TextWrapping="Wrap" Text="v 2.0" VerticalAlignment="Top" Width="52" RenderTransformOrigin="0.392,0.346" BorderThickness="0" Foreground="LightGray" Focusable="False"/>
<PasswordBox Name="Pass_Text" Password="nopassword" Grid.Column="4" HorizontalAlignment="Left" Margin="10,30,0,0" Grid.Row="2" VerticalAlignment="Top" Height="30" Grid.ColumnSpan="2" Width="190" VerticalContentAlignment="Center" TabIndex="1"/>
<Label Content="User Login" Grid.Column="2" HorizontalAlignment="Left" Grid.Row="2" VerticalAlignment="Top" Width="100"/>
<Button Style="{StaticResource LinkButton}" Content="{x:Static properties:Resources.ForgotPass_label}" Grid.Column="2" HorizontalAlignment="Left" Grid.Row="3" VerticalAlignment="Top" Click="ForgotPassword_Click" Width="Auto" Grid.ColumnSpan="2" Margin="0,35,0,0" Focusable="False"/>
<TextBlock Grid.Column="3" HorizontalAlignment="Left" Margin="18,36,0,0" Grid.Row="3" TextWrapping="Wrap" Text=" | " VerticalAlignment="Top" Width="Auto" Height="17" RenderTransformOrigin="2,0.529"/>
<Button Style="{StaticResource LinkButton}" Content="{x:Static properties:Resources.Help_label}" Grid.Column="3" HorizontalAlignment="Left" Margin="36,36,0,0" Grid.Row="3" VerticalAlignment="Top" Width="34" Height="17" Click="Help_Click" RenderTransformOrigin="0.559,1.824" Focusable="False"/>
</Grid>
</Controls:MetroWindow>
I'm sorry this is such a crude example but I hope it shows you how you could solve your problem. Below is a simple example on how to swap two user controls using a button...
You have a shell view:
<Window x:Class="LogonApplication.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:LogonApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ChangePasswordViewModel}">
<local:ChangePasswordView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:EnterPasswordViewModel}">
<local:EnterPasswordView />
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="False">
<ContentPresenter Content="{Binding ActiveView}"
DockPanel.Dock="Top"/>
<StackPanel DockPanel.Dock="Bottom">
<Button Content="Change View" Command="{Binding ChangeViewCommand}"/>
</StackPanel>
</DockPanel>
</Window>
Bound to a view model like this:
public class MainWindowViewModel : ViewModelBase
{
ViewModelBase _activeView;
public MainWindowViewModel()
{
_activeView = new EnterPasswordViewModel();
ChangeViewCommand = new ChangeViewCommand(this);
}
public ViewModelBase ActiveView
{
get { return _activeView; }
set
{
_activeView = value;
OnPropertyChanged("ActiveView");
}
}
public ICommand ChangeViewCommand { get; set; }
}
Then all you need to do is change the viewmodel being displayed by your content presenter. This could be achieved by wiring up a command to toggle your user controls/view models.
class ChangeViewCommand : ICommand
{
private MainWindowViewModel _mainWindowViewModel;
public event EventHandler CanExecuteChanged;
public ChangeViewCommand(MainWindowViewModel mainWindowViewModel)
{
_mainWindowViewModel = mainWindowViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (_mainWindowViewModel.ActiveView.GetType() == typeof(EnterPasswordViewModel))
{
_mainWindowViewModel.ActiveView = new ChangePasswordViewModel();
}
else
{
_mainWindowViewModel.ActiveView = new EnterPasswordViewModel();
}
}
}
Heres the base class for completeness:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
I havent shown the other two views and view models but the views are just WPF User controls and the view models are classes that implement viewmodelbase. Let me know if you need more information.

MVVM-Light Different binding source triggers all ViewModels in context

I'm getting a strange behavior in a test model for mvvm-light:
I created one Window and setted two resources, one grid with two distinct dataContext, the problem is that the two view models are receiving the response even if I'm using only the side of one dataContext. Is there a way to set only the specified data context to fire to the view model that I indicate?
XAML Code:
<Window x:Class="POCWPFValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:POCWPFValidation.ViewModel"
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<vm:MainViewModel x:Key="Main"/>
<vm:SecondaryView x:Key="Sec"/>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical" DataContext="{Binding Source={StaticResource Main}}">
<CheckBox Content="CheckBox" IsChecked="{Binding CheckBox1Checked}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<CheckBox Content="CheckBox" IsChecked="{Binding CheckBox2Checked}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<CheckBox Content="CheckBox" IsChecked="{Binding CheckBox3Checked}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<CheckBox Content="CheckBox" IsChecked="{Binding CheckBox4Checked}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<TextBox Text="{Binding StringTest, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Button" Command="{Binding Teste}" HorizontalAlignment="Left" VerticalAlignment="Top" Background="{x:Null}" DataContext="{Binding Mode=OneWay}"/>
<Button Content="Button" Command="{Binding Teste2}" CommandParameter="{Binding ElementName=TextBox1, Path=Text}" HorizontalAlignment="Left" VerticalAlignment="Top" Background="{x:Null}" DataContext="{Binding Mode=OneWay}"/>
</StackPanel>
<StackPanel Orientation="Vertical" DataContext="{Binding Source={StaticResource Sec}}">
<CheckBox Content="CheckBox" IsChecked="{Binding CheckBox1Checked}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<CheckBox Content="CheckBox" IsChecked="{Binding CheckBox2Checked}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<CheckBox Content="CheckBox" IsChecked="{Binding CheckBox3Checked}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<CheckBox Content="CheckBox" IsChecked="{Binding CheckBox4Checked}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<TextBox Text="{Binding StringTest, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Button" Command="{Binding Teste}" HorizontalAlignment="Left" VerticalAlignment="Top" Background="{x:Null}" DataContext="{Binding Mode=OneWay}"/>
<Button Content="Button" Command="{Binding Teste2}" CommandParameter="{Binding ElementName=TextBox1, Path=Text}" HorizontalAlignment="Left" VerticalAlignment="Top" Background="{x:Null}" DataContext="{Binding Mode=OneWay}"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
The views are the same, so just change the name to SecondaryView
namespace POCWPFValidation.ViewModel
{
public class MainViewModel : ViewModelBase
{
private bool m_blnCheckBox1Checked;
private bool m_blnCheckBox2Checked;
private bool m_blnCheckBox3Checked;
private bool m_blnCheckBox4Checked;
public bool CheckBox1Checked
{
get { return m_blnCheckBox1Checked; }
set
{
Set(ref m_blnCheckBox1Checked, value, true);
}
}
public bool CheckBox2Checked
{
get { return m_blnCheckBox2Checked; }
set
{
Set(ref m_blnCheckBox2Checked, value, true);
}
}
public bool CheckBox3Checked
{
get { return m_blnCheckBox3Checked; }
set
{
Set(ref m_blnCheckBox3Checked, value, true);
}
}
public bool CheckBox4Checked
{
get { return m_blnCheckBox4Checked; }
set
{
Set(ref m_blnCheckBox4Checked, value, true);
}
}
string m_strStringTest = string.Empty;
public string StringTest
{
get { return m_strStringTest; }
set
{
Set(ref m_strStringTest, value, true);
}
}
public RelayCommand<string> Teste2 { get; private set; }
public RelayCommand Teste { get; private set; }
public MainViewModel()
{
Teste = new RelayCommand(MyAction, MyCanExecute);
Teste2 = new RelayCommand<string>(MyAction2, MyCanExecute2);
}
private void MyAction()
{
}
private void MyAction2(string seila)
{
}
private bool MyCanExecute2(string teste)
{
return !string.IsNullOrWhiteSpace(teste);
}
private bool MyCanExecute()
{
if (CheckBox1Checked && CheckBox2Checked && CheckBox3Checked && CheckBox4Checked && StringTest.Equals("teste"))
{
return true;
}
return false;
}
}
}
EDIT:
Sorry, I was not clear with my question, let me try again: like I told before, I have two view models and two distinct datacontext in mainWindow one for each viewmodel. Every time some property on any of these view models changes all the canExecute method of the two viewmodels are triggered. Change some property on view A and the can execute will be triggered on views A and B. Is there a way to only trigger at the specific CanExecute responsible view?
CanExecute is not triggered by the property changes but by CommandManager.RequerySuggested event. Don't worry about when its invoked WPF handles that

Categories