WPF Databinding searching in wrong DataContext - c#

i'm learning the MVVM Pattern and have some trouble with Databindings. I understand how it works, but in my example it's using the wrong DataContext to search for the binding.
I did not found another Question that suits to my problem.
So i have this View:
<UserControl x:Class="Kenshinaro.CashRegister.UI.View.CashRegister.CashRegisterChoiceView"
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">
<ItemsControl ItemsSource="{Binding CashRegisters}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</UserControl>
With following ViewModel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Kenshinaro.CashRegister.UI.View.CashRegister;
namespace Kenshinaro.CashRegister.UI.ViewModel.CashRegister
{
public class CashRegisterChoiceViewModel : MVVM.ViewModel
{
private ObservableCollection<CashRegisterItemsControlView> _cashRegisters = new ObservableCollection<CashRegisterItemsControlView>()
{
new CashRegisterItemsControlView()
{
DataContext = new CashRegisterItemsControlViewModel()
{
View = new CashRegisterCardView()
{
DataContext = new CashRegisterCardViewModel()
{
Model = new Model.CashRegister()
{
Name = "1"
}
}
}
}
},
new CashRegisterItemsControlView()
{
DataContext = new CashRegisterItemsControlViewModel()
{
View = new CashRegisterCardView()
{
DataContext = new CashRegisterCardViewModel()
{
Model = new Model.CashRegister()
{
Name = "2"
}
}
}
}
},
new CashRegisterItemsControlView()
{
DataContext = new CashRegisterItemsControlViewModel()
{
View = new CashRegisterCardView()
{
DataContext = new CashRegisterCardViewModel()
{
Model = new Model.CashRegister()
{
Name = "3"
}
}
}
}
}
};
public ObservableCollection<CashRegisterItemsControlView> CashRegisters
{
get => _cashRegisters;
set => SetProperty(ref _cashRegisters, value);
}
}
}
(My Base ViewModel class is implementing the INotifyPropertyChanged interface)
So this View shows a list of this view:
<UserControl x:Class="Kenshinaro.CashRegister.UI.View.CashRegister.CashRegisterItemsControlView"
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:Kenshinaro.CashRegister.UI.View.CashRegister"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<md:Card Margin="10">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<local:CashRegisterCardView Padding="5"/>
<Button Margin="20" Grid.Row="1" Content="Pick me"/>
</Grid>
</md:Card>
</UserControl>
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Kenshinaro.CashRegister.UI.ViewModel.CashRegister
{
public class CashRegisterItemsControlViewModel : MVVM.ViewModel
{
private View.CashRegister.CashRegisterCardView _view;
public View.CashRegister.CashRegisterCardView View
{
get => _view;
set => SetProperty(ref _view, value);
}
}
}
And this View shows also this view:
<UserControl x:Class="Kenshinaro.CashRegister.UI.View.CashRegister.CashRegisterCardView"
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:Kenshinaro.CashRegister.UI.View.CashRegister"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Background="White" >
<Grid>
<TextBlock Text="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=local:CashRegisterCardView}}"/>
</Grid>
</UserControl>
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Kenshinaro.CashRegister.Model;
namespace Kenshinaro.CashRegister.UI.ViewModel.CashRegister
{
public class CashRegisterCardViewModel : MVVM.ViewModel
{
private Model.CashRegister _model;
public Model.CashRegister Model
{
get => _model;
set => SetProperty(ref _model, value);
}
public String Name
{
get => _model.Name;
set => _model.Name = value;
}
}
}
Now if I start the App the Binding on the TextBlock in the last view doesn't appear.
The Output of VisualStudio says:
System.Windows.Data Error: 40 : BindingExpression path error: 'Name' property not found on 'object' ''CashRegisterItemsControlViewModel' (HashCode=35528341)'. BindingExpression:Path=DataContext.Name; DataItem='CashRegisterCardView' (Name=''); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
As I can get out of it, it is searching for the Property in the DataContext of CashRegisterItemsControlView but I just don't understand why, because i set the DataContext manually on collection initialization (Just for testing):
private ObservableCollection<CashRegisterItemsControlView> _cashRegisters = new ObservableCollection<CashRegisterItemsControlView>()
{
new CashRegisterItemsControlView()
{
DataContext = new CashRegisterItemsControlViewModel()
{
View = new CashRegisterCardView()
{
DataContext = new CashRegisterCardViewModel()
{
Model = new Model.CashRegister()
{
Name = "1"
}
}
}
}
},
...
So why it's still taking the wrong DataContext?
Now when i change the Binding to View.DataContext.Name it's searching in the manually set DataContext:
System.Windows.Data Error: 40 : BindingExpression path error: 'View' property not found on 'object' ''CashRegisterCardView' (Name='')'. BindingExpression:Path=View.DataContext.Name; DataItem='CashRegisterCardView' (Name=''); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
I'm really confused.. I hope you can help me.
Thanks!
If you need information about the other classes just ask. I don't want to put more code into the question as it just long enough.

View models have references to views. This is a violation of MVVM pattern.
The root of problem is in the CashRegisterItemsControlView implementation:
<UserControl x:Class="Kenshinaro.CashRegister.UI.View.CashRegister.CashRegisterItemsControlView"
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:Kenshinaro.CashRegister.UI.View.CashRegister"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<md:Card Margin="10">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<local:CashRegisterCardView Padding="5"/>
<Button Margin="20" Grid.Row="1" Content="Pick me"/>
</Grid>
</md:Card>
</UserControl>
it creates an instance of CashRegisterCardView (<local:CashRegisterCardView Padding="5"/>) which doesn't have its own DataContext, and inherits DataContext from parent. View from CashRegisterItemsControlViewModel is not used.
You can change it to make it work, like this:
<UserControl x:Class="Kenshinaro.CashRegister.UI.View.CashRegister.CashRegisterItemsControlView"
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:Kenshinaro.CashRegister.UI.View.CashRegister"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<md:Card Margin="10">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentControl Content="{Binding View}" Padding="5"/>
<Button Margin="20" Grid.Row="1" Content="Pick me"/>
</Grid>
</md:Card>
</UserControl>
but it would be much better to rework your app architecture

Related

Custom Property of User Control not accessible in WPF C#

I want to create a Custom User control (UserControl) with custom property (MyLabel) in WPF using C# without writing any code behind. But my custom property MyLabel is inaccessible in MainWindow.xaml when I'm using my custom control. What is the problem in my code? If my implementation is wrong then how to achieve this?
UCControl.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public class UCControl:UserControl
{
public String MyLabel
{
get { return (String)GetValue(MyLabelProperty); }
set { SetValue(MyLabelProperty, value); }
}
public static readonly DependencyProperty MyLabelProperty =
DependencyProperty
.Register(
"MyLabel",
typeof(string),
typeof(UCControl),
new PropertyMetadata(""));
public UCControl()
{
MyLabel = "default label";
}
}
}
UserControl1.xaml
<UserControl x:Class="WpfApp1.UserControl1"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.DataContext>
<local:UCControl/>
</Grid.DataContext>
<TextBlock Text="{Binding MyLabel}" FontSize="18"/>
</Grid>
</UserControl>
MainWindow.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:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid >
<local:UserControl1 MyLabel="Hello World"/>
</Grid>
</Window>
The expression
<local:UserControl1 MyLabel="Hello World"/>
requires that MyLabel is a property of the UserControl1 class.
You have to declare the property of the class declaration of UserControl1, i.e. in the file UserControl1.xaml.cs:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public static readonly DependencyProperty MyLabelProperty =
DependencyProperty.Register(
nameof(MyLabel),
typeof(string),
typeof(UserControl1));
public string MyLabel
{
get { return (string)GetValue(MyLabelProperty); }
set { SetValue(MyLabelProperty, value); }
}
}
You would bind to that property in the UserControl's XAML by
<TextBlock Text="{Binding MyLabel,
RelativeSource={RelativeSource AncestorType=UserControl}}" />

ContentControl not refreshing View when new ViewModel is created

I am trying to make an app using using MVVMLight that has a sidebar with buttons, each button shows a new usercontrol hosted in a ContentControl. When the user clicks the buttons in the sidebar (Show View1 / Show View2) it shows the views correctly.
My problems arise when the user has already shown a View (it could be View1 or View2 ), say View1 and click (Show View1) ** again ** View1 controls remain with the same values and I would like to show the same view with all controls as if it had restarted. (I know that I could reset all controls within the user control (View1) but my application requires having a new instance of the view every time the button is clicked).
Somehow, in the same scenario when the user is in View 1, switch to view 2 and return to view 1, a new instance is created and shown as I would like. I would like to get the same result without the need to switch to view 2 and return.
Here is a minimal reproducible example of the issue:
ViewModelLocator.cs
using CommonServiceLocator;
using GalaSoft.MvvmLight.Ioc;
namespace SidebarApp.ViewModel
{
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// Register viewmodels
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<View1ViewModel>();
SimpleIoc.Default.Register<View2ViewModel>();
}
public MainViewModel Main { get { return ServiceLocator.Current.GetInstance<MainViewModel>(); } }
public View1ViewModel View1Vm { get { return ServiceLocator.Current.GetInstance<View1ViewModel>(); } }
public View2ViewModel View2Vm { get { return ServiceLocator.Current.GetInstance<View2ViewModel>(); } }
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
}
MainViewModel.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Windows.Input;
namespace SidebarApp.ViewModel
{
public class MainViewModel : ViewModelBase
{
// Commands
public ICommand ShowView1Command { get; private set; }
public ICommand ShowView2Command { get; private set; }
/// <summary>
/// Property that will cointain the current viewmodel to show
/// ViewModelBase inplements ObservableObject class which imlements INotifyPropertyChanged
/// </summary>
public ViewModelBase CurrentViewModel { get; set; }
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
this.ShowView1Command = new RelayCommand(ShowView1);
this.ShowView2Command = new RelayCommand(ShowView2);
}
private void ShowView1()
{
// I tried this but it doesn't work
//CurrentViewModel = null or View2ViewModel;
CurrentViewModel = new View1ViewModel();
}
private void ShowView2()
{
CurrentViewModel = new View2ViewModel();
}
}
}
MainWindow.xaml
<Window x:Class="SidebarApp.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:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Title="MainWindow" Height="348.965" Width="560.683">
<Window.Resources>
<DataTemplate DataType="{x:Type local:View1ViewModel}">
<local:View1View />
</DataTemplate>
<DataTemplate DataType="{x:Type local:View2ViewModel}">
<local:View2View />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Button Command="{Binding ShowView1Command}">Show View1</Button>
<Button Command="{Binding ShowView2Command}">Show View2</Button>
</StackPanel>
<ContentControl Grid.Column="1" Content="{Binding Path=CurrentViewModel}"></ContentControl>
</Grid>
</Window>
View1View.xaml
<UserControl x:Class="SidebarApp.View1View"
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:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding View1Vm, Source={StaticResource Locator}}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightPink">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 1" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="Original text value"/>
<CheckBox Content="Some boolean value"/>
</StackPanel>
</Grid>
</UserControl>
View1ViewModel.cs
using GalaSoft.MvvmLight;
namespace SidebarApp
{
public class View1ViewModel : ViewModelBase
{
public View1ViewModel()
{
}
}
}
View2View.xaml
<UserControl x:Class="SidebarApp.View2View"
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:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding View2Vm, Source={StaticResource Locator}}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightCyan">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 2" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="Some text in view2"/>
<Button Content="Button"/>
</StackPanel>
</Grid>
</UserControl>
View2ViewModel.cs
using GalaSoft.MvvmLight;
namespace SidebarApp
{
public class View2ViewModel : ViewModelBase
{
public View2ViewModel()
{
}
}
}
App.xaml
<Application x:Class="SidebarApp.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SidebarApp" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:SidebarApp.ViewModel" />
</ResourceDictionary>
</Application.Resources>
</Application>
For example I enter to the view1 and change the value of the textbox ("Original text value") to another value and I click again in Show View1 the text and everything in the usercontrol stays the same but when I switch to view2 and go back to view1 a new usercontrol is shown with his original values.
This might feel like something of a workaround, but it works. Hopefully it will prove useful. (Obviously make same changes to View2View view/view model).
ViewModelLocator.cs
public View1ViewModel View1Vm { get { return new View1ViewModel(); } }
MainViewViewModel.cs
private void ShowView1()
{
CurrentViewModel = new ViewModelLocator().View1Vm;
}
View1View.xaml
<UserControl x:Class="SidebarApp.View1View"
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:SidebarApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightPink">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 1" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="{Binding Path=View1Text, Mode=TwoWay}"/>
<CheckBox Content="Some boolean value" IsChecked="{Binding Path=CheckBoxChecked, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</UserControl>
View1ViewModel.cs
public class View1ViewModel : ViewModelBase
{
private string _view1Text;
private bool _checkBoxedChecked;
public string View1Text
{
get
{
return _view1Text;
}
set
{
_view1Text = value;
RaisePropertyChanged("View1Text");
}
}
public bool CheckBoxChecked
{
get
{
return _checkBoxedChecked;
}
set
{
_checkBoxedChecked = value;
RaisePropertyChanged("CheckBoxChecked");
}
}
public View1ViewModel()
{
View1Text = "Original text value";
}
}

How to switch Usercontrol(s) in MainWindow.xaml using ICommand Mvvm pattern?

Please -- I do not want to employ/use outside frameworks like MVVMlite (etc) at this time. I need to do this manually so that I can see the full process.
I have seen various articulations of the question which I am asking, but I have not seen any versions of my question which bind a command to a usercontrol to change out a usercontrol in MainWindow.xaml. In the following code I demonstrate the effort/attempt I have tried to make a Wpf/Mvvm application for switching out usercontrols in MainWindow.xaml. The question/request is what steps I need to take to follow through with this project?
In my project I have the standard Models/ViewModels/Views folders, 3 usercontrol views that I want to switch around in MainWindow.xaml (MainWindow.xaml resides in the root folder of the project) -- BlueView, OrangeView, RedView. The only content these views/usercontrols have is that Blueview has a blue background grid, OrangeView has an orange background grid, RedView has a red background grid. I have 3 buttons in a stackpanel to the left in MainWindow.xaml and a content control where I want to load/switch the usercontrols in the right of MainWindow.xaml. I have 3 corresponding ViewModels, BlueViewModel, OrangeViewModel, RedViewModel. I also have a MainViewModel for tying up these 3 viewModels, and RelayCommand.cs in the Models folder. But I don't know where to go from there.
Here is my code -- note: I'm only going to add MainWindow.xaml, RelayCommand.cs, MainViewModel and BlueViewModle/BlueView since the other views/ViewModels are the same except for the background grid color . What do I need to do/add so that I can load/switch the usercontrols in the content control in MainWindow.xaml? I can't show a usercontrol - so I don't have a show/display method in MainViewModel.cs How do I load the usercontrols? Do I need methods in the ViewModels?
--MainWindow.xaml -- resides in the project root folder
<Window x:Class="ViewChangerFromICommand.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:ViewChangerFromICommand"
xmlns:viewmodels="clr-namespace:ViewChangerFromICommand.ViewModels"
xmlns:views="clr-namespace:ViewChangerFromICommand.Views"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Name="redViewTemplate" DataType="{x:Type viewmodels:RedViewModel}">
<views:RedView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="BlueViewTemplate" DataType="{x:Type viewmodels:BlueViewModel}">
<views:BlueView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="OrangeViewTemplate" DataType="{x:Type viewmodels:OrangeViewModel}">
<views:OrangeView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewmodels:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Background="Gray" Grid.Row="0" Grid.Column="0" Grid.RowSpan="5">
<StackPanel>
<Button Content="Red View"/>
<Button Content="Blue View"/>
<Button Content="Orange View"/>
</StackPanel>
</DockPanel>
<ContentControl Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="4" Grid.RowSpan="5" Content="{Binding}"/>
</Grid>
</Window>
--RelayCommand.cs -- resides in Models folder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace ViewChangerFromICommand.Models
{
public class RelayCommand : ICommand
{
readonly Action _execute;
readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new NullReferenceException("execute");
_execute = execute;
_canExecute = canExecute;
}
public RelayCommand(Action execute) : this(execute, null)
{
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public void Execute(object parameter)
{
_execute.Invoke();
}
}
}
--MainViewModel.cs -- resides in ViewModels folder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ViewChangerFromICommand.Models;
using ViewChangerFromICommand.Views;
namespace ViewChangerFromICommand.ViewModels
{
public class MainViewModel
{
public BlueViewModel blueVM { get; set; }
public OrangeViewModel orangeVM { get; set; }
public RedViewModel redVM { get; set; }
public MainViewModel()
{
blueVM = new BlueViewModel();
orangeVM = new OrangeViewModel();
redVM = new RedViewModel();
}
}
}
--BlueViewModel.cs -- resides in ViewModels folder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using ViewChangerFromICommand.Models;
using ViewChangerFromICommand.Views;
namespace ViewChangerFromICommand.ViewModels
{
public class BlueViewModel
{
public BlueViewModel()
{
}
}
}
--BlueView.xaml -- resides in Views folder
<UserControlx:Class="ViewChangerFromICommand.Views.BlueView"
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:ViewChangerFromICommand.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="Blue">
</Grid>
</UserControl>
Consider the following approach.
In MainViewModel create 'Selected' property, which will reflect which ViewModel/View you want to see. Setup RelayCommands to assign desired view model to the 'Selected' property.
In view (Window), bind Content of the ContentControl to 'Selected' and setup command bindings.
MainViewModel also needs to implement INotifyPropertyChanged for change to 'Selected' property to be recognized by the view.
View model:
public class MainViewModel:INotifyPropertyChanged //Look into using Prism and BindableBase instead of INotifyPropertyChanged
{
private BlueViewModel blueVM;
private OrangeViewModel orangeVM;
private RedViewModel redVM;
public event PropertyChangedEventHandler PropertyChanged=delegate { };
object selectedView;
public object SelectedView
{
get { return selectedView; }
private set
{
selectedView = value;
RaisePropertyChanged("SelectedView");
}
}
public ICommand SelectBlueViewCommand { get; private set; }
public ICommand SelectOrangeViewCommand { get; private set; }
public ICommand SelectRedViewCommand { get; private set; }
public MainViewModel()
{
blueVM = new BlueViewModel();
orangeVM = new OrangeViewModel();
redVM = new RedViewModel();
SelectBlueViewCommand = new RelayCommand(() => SelectedView = blueVM);
SelectOrangeViewCommand = new RelayCommand(() => SelectedView = orangeVM);
SelectRedViewCommand = new RelayCommand(() => SelectedView = redVM);
}
void RaisePropertyChanged(string property)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
View/Window
<Window x:Class="WpfApp1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:ViewChangerFromICommand"
xmlns:viewmodels="clr-namespace:ViewChangerFromICommand.ViewModels"
xmlns:views="clr-namespace:ViewChangerFromICommand.Views"
Title="Window1" Height="650" Width="750">
<Window.Resources>
<DataTemplate x:Name="redViewTemplate" DataType="{x:Type viewmodels:RedViewModel}">
<views:RedView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="BlueViewTemplate" DataType="{x:Type viewmodels:BlueViewModel}">
<views:BlueView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="OrangeViewTemplate" DataType="{x:Type viewmodels:OrangeViewModel}">
<views:OrangeView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewmodels:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Background="Gray" Grid.Row="0" Grid.Column="0" Grid.RowSpan="5">
<StackPanel>
<Button Content="Red View" Command="{Binding SelectBlueViewCommand}"/>
<Button Content="Blue View" Command="{Binding SelectOrangeViewCommand}"/>
<Button Content="Orange View" Command="{Binding SelectRedViewCommand}"/>
</StackPanel>
</DockPanel>
<ContentControl Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="4" Grid.RowSpan="5"
Content="{Binding SelectedView}"/>
</Grid>

How to get datacontext used in another xaml

I have a test application wich consists on two Windows and a UserControl.
I want to insert the control in each Window using the same DataContext:
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.View"
xmlns:viewModel="clr-namespace:WpfApplication2.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModel:ControlColorViewModel x:Name="dataContext1"/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Width="64" Height="64" Command="{Binding
Path=PressedButton}">Press</Button>
<view:ControlColor Grid.Column="1" />
</Grid>
</Window>
ControlColor.xaml:
<UserControl x:Class="WpfApplication2.View.ControlColor"
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:WpfApplication2.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="{Binding Path=BackgroundColor}">
</Grid>
</UserControl>
ControlColorViewModel.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows.Input;
namespace WpfApplication2.ViewModel
{
class ControlColorViewModel : ViewModelBase
{
private Brush backgroundColor;
public Brush BackgroundColor
{
get { return this.backgroundColor; }
set
{
if (this.backgroundColor != value)
{
this.backgroundColor = value;
OnPropertyChanged("BackgroundColor");
}
}
}
public ICommand PressedButton { get { return new RelayCommand(param =>
this.SetPressedButton()); } }
public ControlColorViewModel()
{
}
private void SetPressedButton()
{
BackgroundColor = Brushes.Orange;
}
}
}
Window2.xaml:
<Window x:Class="WpfApplication2.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:view="clr-namespace:WpfApplication2.View"
Title="Window2" Height="300" Width="300">
<Grid>
<view:ControlColor />
</Grid>
</Window>
When the button is pressed the background in the ContentControl in the MainWindow gets orange, and i want the same for the ContentControl inserted in Window2. Using the same datacontext.
How can i get the same datacontext used in MainWindow?
Thanks in advance.
There are a lot of ways. The easy is to have a common, shared model property (CommonModel) and send it to every ViewModels.
Next step is to use EventAggregator, but to my mind it's too complicated for you..
I'm a little new to WPF (having more of a WinForms background), but if I understand correctly you could define your dataContext1 in the App file resources and then they both could reference it.
You can use something called Dependency Injection to get your controls to use the same DataContext.
Basically, you need to take it out of the XAML and 'find/resolve' your shared ControlColorViewModel in the constructor for your controls.

Add a usercontrol to caliburm micro dynamically

I am using Caliburn Micro with WPF. I want to create an application with a menu on the left side, and a grid on the right side of the application. When clicking on a menu item, the grid on the right side, will change to another view. The another view will be in a separate file.
MainWindowView:
<UserControl x:Class="CMDemo.Views.MainWindowView"
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="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90*" />
<ColumnDefinition Width="210*" />
</Grid.ColumnDefinitions>
<StackPanel Name="LeftMenu">
<Button Name="ChangeDisplay" Content="Click Me"></Button>
<TextBlock x:Name="MyString"></TextBlock>
</StackPanel>
<Grid Grid.Column="1" x:Name="MainGridContent" />
</Grid>
MainWindowViewModel:
public class MainWindowViewModel : PropertyChangedBase
{
private UserControl mainGridContent;
private string myString;
public UserControl MainGridContent
{
get { return this.mainGridContent; }
set
{
this.mainGridContent = value;
NotifyOfPropertyChange(() => this.MainGridContent);
}
}
public string MyString
{
get { return this.myString; }
set
{
this.myString = value;
NotifyOfPropertyChange(() => this.MyString);
}
}
public void ChangeDisplay()
{
this.MainGridContent = new ChangeDisplayView();
this.MyString = "Testing....";
}
}
The changeDisplayViewModel:
public class changeDisplayViewModel: PropertyChangedBase
{
}
The changeDisplayView:
<UserControl x:Class="CMDemo.Views.changeDisplayView"
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="300" d:DesignWidth="300">
<Grid>
<TextBox content="Hello Caliburn Micro">
</Grid>
When I click the "Click Me" button the TextBlock "MyString" is updated and showing, but the usercontrol is not. What am I doing wrong?
Try changing MainGridContent to a changeDisplayViewModel rather than the view itself.

Categories

Resources