how to create Master Page in MVVM? - c#

I want to create master page in mvvm. I created a viewbox that it's name is container for showing my usercontrols and I have two classes, RelayCommand and ViewModel.
Here is my code:
public class ViewModel
{
MainWindow objMainWindow = new MainWindow();
UserControls.History objHistory = new UserControls.History();
UserControls.NewItem objNewItem = new UserControls.NewItem();
UserControls.SideEffect objSideEffect = new UserControls.SideEffect();
public ViewModel()
{
OpenCommand = new RelayCommand(Open);
}
private ICommand openCommand;
public ICommand OpenCommand
{
get { return openCommand; }
set { openCommand = value; }
}
public void Open(object sender)
{
if (sender.ToString() == "btnHistory")
{
objMainWindow.Container.Child = objHistory;
}
if (sender.ToString() == "btnNewItem")
{
}
if (sender.ToString() == "btnSideEffect")
{
}
}
}
And this is my RelayCommand:
public class RelayCommand:ICommand
{
public RelayCommand(Action<object> _action)
{
actionCommand = _action;
}
private Action<object> actionCommand;
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (parameter !=null)
{
actionCommand(parameter);
}
else
{
actionCommand("Null");
}
}
}
but when I run solution I faced with NullRefrenceException when it wanted to show my child of container.
I don't know how to make this work.

Your MainWindow instantiates when your program starts. So you shouldn't instantiate it again in your ViewModel (i.e. this line: MainWindow objMainWindow = new MainWindow();). You should use DataBinding instead.
Here is a sample code that gives you an idea:
First define a property of type FrameworkElement in you ViewModel and set it's value to your desired UserControl in the Open method.
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
FrameworkElement _myUc;
public FrameworkElement MyUserControl
{
get
{
return _myUc;
}
set
{
_myUc= value;
OnPropertyChanged("MyUserControl");
}
}
public ViewModel()
{
OpenCommand = new RelayCommand(Open);
}
public void Open(object sender)
{
if (sender.ToString() == "btnHistory")
{
MyUserControl = objHistory;
}
}
// rest of your view model ...
}
Then instantiate your ViewModel as the DataContext of your MainWindow in the Constructor.
MainWindow:
public ViewModel MyViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
MyViewModel = new ViewModel();
DataContext = MyViewModel;
}
And Finally use a ContentControl (instead of ViewBox) [see my note] and bind it's Content to the MyUserControl property of your ViewModel.
XAML:
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Content="{Binding MyUserControl}" x:Name="Container"/>
<Button Grid.Row="1" Name="btnHistory" Content="ShowHistory" Command="{Binding OpenCommand}" />
</Grid>
This way each time MyUserControl changes, the ContentControl shows your desired UserControl.
Note that Child property of ViewBox is not a DependencyProperty and thus not bind-able.

Related

Binding a property of UserControl with ViewModel programatically

I have a window called SettingsWindow and I have some user controls that can be content of the window. I have a ContentControl and I have a method in view-model that returns new instance of user control to ContentControl's content. I need to set binding properties of user control to view-model programatically.
<Window x:Class="KnitterNotebook.Views.Windows.SettingsWindow"
<Window.Resources>
<viewModels:SettingsViewModel x:Key="SettingsViewModel" />
</Window.Resources>
<Grid DataContext="{StaticResource SettingsViewModel}">
<ContentControl Content="{Binding WindowContent, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
public partial class UserSettingsUserControl : UserControl
{
public UserSettingsUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty NewNicknameProperty =
DependencyProperty.Register(nameof(NewNickname), typeof(string), typeof(UserSettingsUserControl),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string NewNickname
{
get { return GetValue(NewNicknameProperty).ToString()!; }
set { SetValue(NewNicknameProperty, value); }
}
public static readonly DependencyProperty ChangeNicknameCommandAsyncProperty =
DependencyProperty.Register(nameof(ChangeNicknameCommandAsync), typeof(ICommand), typeof(UserSettingsUserControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public ICommand ChangeNicknameCommandAsync
{
get { return (GetValue(ChangeNicknameCommandAsyncProperty) as ICommand)!; }
set { SetValue(ChangeNicknameCommandAsyncProperty, value); }
}
}
public class SettingsViewModel : BaseViewModel
{
public SettingsViewModel()
{
WindowContent = new UserSettingsUserControl();
ChooseSettingsWindowContentCommand = new RelayCommand<Type>(ChooseSettingsWindowContent!);
ChangeNicknameCommandAsync = new AsyncRelayCommand(ChangeNicknameAsync);
}
private string newNickname;
public string NewNickname
{
get { return newNickname; }
set { newNickname = value; OnPropertyChanged(); }
}
public ICommand ChooseSettingsWindowContentCommand { get; private set; }
public ICommand ChangeNicknameCommandAsync { get; set; }
private void ChooseSettingsWindowContent(Type userControl)
{
if (userControl == typeof(UserSettingsUserControl))
{
WindowContent = new UserSettingsUserControl()
{
NewNickname = NewNickname,
ChangeNicknameCommandAsync = ChangeNicknameCommandAsync
};
}
}
Please take a look at private void ChooseSettingsWindowContent(Type userControl). When I use Nickname = Nickname etc., the element is not binded to view-model. I need to set binding programatically. I can't create a new instance of user control in window, because I want to return user control from the method. I read about Binding class and BindingOperations but I still can't solve how to implement it. How can I set bindings programatically in ChooseSettingsWindowContent?

Create a ViewModel with sub ViewModel

Is there a proper way to create a C#/WPF ViewModel containing subViewModel ?
Objective is:
I have a MainWindow. That window is use to read/create images. There is a button on that windows who switch between 2 UserControl one with IHM used to read image, the other one used to create.
The MainWindow has a MainWindowViewModel with :
command switch
image length
application parameters
I want that both UserControls can acces to MainWindowViewModel field/properties and have they own commands.
Construction will be something like this:
public partial class ReadUserControl : UserControl
{
public ReadUserControl()
{
InitializeComponent();
DataContext = MainViewModel.ReadViewModel;
}
}
public partial class CreateUserControl : UserControl
{
public CreateUserControl()
{
InitializeComponent();
DataContext = MainViewModel.CreateViewModel;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = MainViewModel;
}
}
For example, if a MainViewModel contain a field ImageWidth setting ImageWidth in CreateUserControl change the value for ReadUserControl.
I hope to have been clear, I don't know how design my MainViewModel to achieve this result
EDIT1:
I've created the MainWindowViewModel as a Singleton but i'm still unable to get MainViewModel.CreateViewModel and MainViewModel.ReadViewModel
public class MainWindowViewModel : ViewModelBase
{
private static MainWindowViewModel _instance = null;
public static MainWindowViewModel Instance
{
get
{
if (_instance == null)
_instance = new MainWindowViewModel();
return _instance;
}
}
private MainWindowViewModel()
: base()
{
}
#region CreateViewModel
/* How to create ? */
#endregion
#region ReadViewModel
/* How to create ? */
#endregion
}
Your example will work. At least if you have made your MainViewModel a Singleton.
A more professional approach might be an Constructor-Injection like this.
public partial class ReadUserControl : UserControl
{
public ReadUserControl(MainViewModel vm)
{
InitializeComponent();
DataContext = vm.ReadViewModel;
}
}
With such DependencyInjections you can achieve a higher level of abstraction, since your UserControls can be generalized. (They will all have the same Constructor)
On the other hand, you give every such UserControl the ability, to manipulate the MainViewModel, not aware of side-effects.
In your special case, it would be more safe, to pass only the needed parameters to the UserControl, instead of giving them a bunch of informations, they will never need.
public partial class ReadUserControl : UserControl
{
public ReadUserControl(Icommand command, int imageLength, AppParams appParams)
{
InitializeComponent();
...
// Do with your Constructorparameters what ever you have to
}
}
Edit:
Here a small, dumb implementation of how it could be done:
Code
public class MainViewModel : INotifyPropertyChanged {
private INotifyPropertyChanged _selectedViewModel;
public MainViewModel() {
var cmd = new RelayCommand(x => {
MessageBox.Show("HelloWorld");
}, x => true);
this.RVM = new ReadViewModel(cmd);
this.WVM = new WriteViewModel(cmd);
this.SelectedViewModel = WVM;
}
private ICommand _switchViewModelCommand;
public ICommand SwitchViewModelCommand => this._switchViewModelCommand ?? (this._switchViewModelCommand = new RelayCommand(x => {
if (this.SelectedViewModel == RVM) {
this.SelectedViewModel = WVM;
return;
}
this.SelectedViewModel = RVM;
}));
public INotifyPropertyChanged SelectedViewModel {
get {
return this._selectedViewModel;
}
set {
if (Equals(value, this._selectedViewModel))
return;
this._selectedViewModel = value;
this.OnPropertyChanged();
}
}
public ReadViewModel RVM {
get; set;
}
public WriteViewModel WVM {
get; set;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ReadViewModel : INotifyPropertyChanged {
public ReadViewModel(ICommand sayHelloCommand) {
this.HelloCommand = sayHelloCommand;
}
public ICommand HelloCommand {
get;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WriteViewModel : INotifyPropertyChanged {
public WriteViewModel(ICommand sayHelloCommand) {
this.HelloCommand = sayHelloCommand;
}
public ICommand HelloCommand {
get;
}
public ICommand HelloMoonCommand => new RelayCommand(x => { MessageBox.Show("Hello Moon"); });
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid Height="200">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ContentControl Content="{Binding SelectedViewModel, UpdateSourceTrigger=PropertyChanged}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ReadViewModel}">
<StackPanel>
<Button Content="Say Hello world" Command="{Binding HelloCommand}"></Button>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:WriteViewModel}">
<StackPanel>
<Button Content="Say Hello world" Command="{Binding HelloCommand}"></Button>
<Button Content="Say Hello Moon" Command="{Binding HelloMoonCommand}"></Button>
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
<Button Content="Switch VM" Command="{Binding SwitchViewModelCommand}" Grid.Row="1"/>
</Grid>
You can pass in the MainViewModel as DataContext for your user control and set the data context of elements as Read/Create model
something like
<Grid> <!--using MainWindowViewModel as data context-->
<Grid DataContext="{Binding Path=CreateViewModel}"> <!--using CreateViewModel as data context-->
.....
</Grid>
<Grid>

Update ViewModel if Model changed

How can i update view model when model change. First solution that i found was subscribe in ViewModel for PropertyChangedEvent of Model. But is it good way? Maybe it exist better way to Notify ViewModel?
Xaml:
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{Binding SomeValue}"/>
<Label Grid.Row="1" Content="{Binding Second}"/>
</Grid>
</Grid>
Code:
namespace WpfApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
Model model = new Model();
public ViewModel()
{
model.PropertyChanged += (o, e) =>
{
PropertyChanged(this, new PropertyChangedEventArgs(e.PropertyName));
};
}
public int SomeValue
{
get
{
return this.model.SomeValue;
}
set
{
this.model.SomeValue = value;
}
}
public int Second
{
get
{
return this.model.Second;
}
set
{
this.model.Second = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Model : INotifyPropertyChanged
{
private int someValue;
public int SomeValue
{
get
{
return this.someValue;
}
set
{
this.someValue = value;
this.RaisePropertyChanged("SomeValue");
}
}
private int second;
public int Second
{
get
{
return this.second;
}
set
{
this.second = value;
this.RaisePropertyChanged("Second");
}
}
public Model()
{
Action Test = new Action(WorkAsync);
IAsyncResult result = Test.BeginInvoke(null,null);
}
public void WorkAsync()
{
while(true)
{
System.Threading.Thread.Sleep(3000);
SomeValue += 1;
Second += 1;
RaisePropertyChanged("SomeValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
Your model does not necessarily need to implement INotifyPropertyChanged, a pure MVVM approach would be to implement INotifyPropertyChanged in the View Model and expose only properties of which the view requires of the Model.
In your case, it'll look like this:
public class Model
{
public int SomeValue { get; set; }
}
public ViewModel : INotifyPropertyChanged
{
private Model _model = new Model();
public int SomeModelValue
{
get { return _model.SomeValue; }
set
{
_model.SomeValue = value;
//Notify property changed
}
}
...
}
And your view will bind to SomeModelValue:
<TextBox Text="{Binding SomeModelValue}" ... />
The benefit of this approach is that your view model is not exposing the entire model to the view, and only properties of the model that need to be exposed will be seen by the view. It also allows your model to remain relatively dumb.
create a raisepropertychanged method/call it
enforce two way binding on the control
look here
and here
and here is best
xaml two way binding

Visiblity binding in WPF on a Grid

While the data is loading, I want to hide the content of the WPF control (which is all contained in a grid) and display a loading trobber.
MainWindow.xaml:
<Grid Visibility="{Binding IsContentVisible}">
<!-- MyCustomView is hosted here -->
</Grid>
The data context of MainWindow is MainWindowViewModel, which also derives from ViewModelBase
public class MainWindowViewModel : ViewModelBase
{}
MyCustomView.xaml:
<Grid>
<TextBox Text="{Binding Data}" />
<TextBlock>This shouldn't be visible during the 2.5 sec loading time, but it is!</TextBlock>
</Grid>
MyCustomViewModel.cs:
public class MyCustomViewModel : ViewModelBase
{
public async void LoadData()
{
IsContentVisible = Visibility.Hidden;
OnPropertyChanged("IsContentVisible");
// Display loading.gif
Data = await repository.LoadData();
// Hide loading.gif
IsContentVisible = Visibility.Visible;
OnPropertyChanged("IsContentVisible");
}
}
ViewModelBase.cs:
public class ViewModelBase : INotifyPropertyChanged
{
private Visbility isContentVisible = Visibility.Visible;
public Visbility IsContentVisible
{
get { return isContentVisible; }
set { isContentVisible = value; }
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ViewModelBase currentViewModel;
public ViewModelBase CurrentViewModel
{
get { return currentViewModel; }
set { SetProperty(ref currentViewModel, value); }
}
}
and repository.LoadData():
Thread.Sleep(2500);
return "some text";
LoadData() is called as a command of a button
What am I doing wrong? If bind visiblity to the grid of the MyCustomView it works, but that's not the solution, since I'd have many views, and I only want to have binding in one place, the MainWindow.
Have you tried this?
private Visbility isContentVisible = Visibility.Visible;
public Visbility IsContentVisible
{
get { return isContentVisible; }
set
{
isContentVisible = value;
OnPropertyChanged("IsContentVisible");
}
}

MetroWindow.RightWindowCommands in dynamically created MetroWindow

How to bind/create MetroWindow.RightWindowCommands in dynamically created MetroWindow through Caliburn.Micro IWindowManager Show method?
For example, I've created a custom IWindowManager implementation to always create MetroWindow instead of default Window. So whenever a new Window is created from Caliburn, it will be MetroWindow instance.
I have a logic that creates dynamically windows through IWindowManager:
ChatManager
public class ChatManager : IChatManager
{
private readonly IChatWindowSettings chatWindowSettings;
private readonly IWindowManager windowManager;
private readonly IChatFactory chatFactory;
private IDictionary<WeakReference, WeakReference> chats;
public ChatManager(IChatWindowSettings chatWindowSettings, IWindowManager windowManager, IChatFactory chatFactory)
{
this.chatWindowSettings = chatWindowSettings;
this.windowManager = windowManager;
this.chatFactory = chatFactory;
chats = new Dictionary<WeakReference, WeakReference>();
}
public void OpenFor(ISender sender)
{
var settings = chatWindowSettings.Create();
var viewModel = CreateOrGetViewModel(sender);
windowManager.ShowWindow(viewModel, null, settings);
}
private IChat CreateOrGetViewModel(ISender sender){//code...}
Those windows are chat windows. This works great. However, I'd like to bind/create a button directly in the MetroWindow RightCommands. This button would be bound to the IChat implementation (which is a view-model):
public class ChatViewModel : Screen, IChat
{
public void DoSomething(){}
}
How can I accomplish such thing?
here are some thoughts for your problem
calling sample
var view = new MainWindow(new ChatViewModel() { ChatName = "Chat name" });
view.Show();
model sample
public class ChatViewModel
{
public string ChatName { get; set; }
private ICommand chatCommand;
public ICommand ChatCommand
{
get
{
return chatCommand
?? (chatCommand = new SimpleCommand() {
CanExecutePredicate = o => true,
ExecuteAction = o => MessageBox.Show("Hurray :-D")
});
}
}
}
window code behind
public partial class MainWindow : MetroWindow
{
public MainWindow(ChatViewModel chatViewModel)
{
this.DataContext = chatViewModel;
InitializeComponent();
}
}
window xaml
<Controls:MetroWindow x:Class="MahAppsMetroSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
Title="MainWindow"
GlowBrush="{DynamicResource AccentColorBrush}"
Height="350"
Width="525">
<Controls:MetroWindow.RightWindowCommands>
<Controls:WindowCommands>
<Button Content="{Binding ChatName}"
Command="{Binding ChatCommand}" />
</Controls:WindowCommands>
</Controls:MetroWindow.RightWindowCommands>
<Grid>
<!-- the content -->
</Grid>
</Controls:MetroWindow>
simple command
public class SimpleCommand : ICommand
{
public Predicate<object> CanExecutePredicate { get; set; }
public Action<object> ExecuteAction { get; set; }
public bool CanExecute(object parameter)
{
if (CanExecutePredicate != null)
return CanExecutePredicate(parameter);
return true; // if there is no can execute default to true
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (ExecuteAction != null)
ExecuteAction(parameter);
}
}
hope that helps

Categories