WPF MVVM switch usercontrols - c#

I am new to MVVM and WPF but I know what's going on in MVVM. I have a problem with switching between user controls in mainwindow. In my app I have:
MainWindow.xaml with log and 2 links: Show all and Create new. Of course I have ViewModel for it. I have 2 more UserControls: ShowAll and Create with ViewModels and all logic in it (adding data etc). How can I show create form when I click link Create new or show all when I click ShowAll?
In windowForms I just hide UC, buto here is no code behind :)
My MainWindow.xaml:
<Window x:Class="Test.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<Grid>
<StackPanel>
<TextBox Text="{Binding Name}"/>
<Button Content="Change" Command="{Binding ChangeCommand}"/>
</StackPanel>
</Grid>
</Window>
My MainWindowViewModel:
class MainWindowViewModel : BaseViewModel
{
private Person _person;
private BaseCommand _changeCommand;
public MainWindowViewModel()
{
_person = new Person();
}
public string Name
{
get
{
return _person.Name;
}
set
{
if (_person.Name != value)
_person.Name = value;
OnPropertyChanged(() => Name);
}
}
public ICommand ChangeCommand
{
get
{
if (_changeCommand == null)
_changeCommand = new BaseCommand(() => change());
return _changeCommand;
}
}
private void change()
{
_person = new Person();
Name = _person.Imie;
}
}
In Create and ShowAll there is no code. In xaml only a label, VM is empty. Just for test.
Thank's for help!

You can use a ContentControl to display a specific DataTemplate based on the type of ViewModel that is bound to the ContentControl.
http://www.japf.fr/2009/03/thinking-with-mvvm-data-templates-contentcontrol/
The command that is bound to the ShowAll button can simply change a property on your main ViewModel which is what is bound to your content control.

Related

Validate Input of UserControl in Window WPF

Currently I have a UserControl contained within a window. The UserControl is made up of two text boxes. The UserControl is an element in my MainWindow. Outside the scope of my UserControl is my submit button in my window. I would like to enable and disable the button whenever the boxes text contents are not null or null.
UserControl XAML code:
<UserControl x:Class="myClass.myUserControl"
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">
<StackPanel Background="White">
<DockPanel>
<Label x:Name="lbl1" Content="First Box:"/>
<TextBox x:Name="txtbox1"/>
<Label x:Name="lbl1" Content="Second Box:"/>
<TextBox x:Name="txtbox2"/>
</DockPanel>
</StackPanel>
</UserControl>
View Model:
using System;
namespace myClass {
partial class UserControlViewModel: ViewModelBase {
private bool _validInput;
public UserControlViewModel() {
validInput = false;
}
public object validInput {
get { return _validInput; }
set {
_validInput = value;
OnPropertyChanged("validInput");
}
}
}
ViewModelBase:
using System.ComponentModel;
namespace myClass {
class ViewModelBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
My issue is deciding on how to handle this validation, my button's isEnabled property is currently bounded to the validInput boolean of the view model. However, the contents of the user control are not accessible in my window as I have abstracted it as a separate userControl item (I plan on having different user controls available to be shown in the window).
MainWindow XAML:
<Window x:Class="myClass.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:myClass"
Title="MainWindow" Height="356" Width="699" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<uc:UserControlViewModel/>
</Window.DataContext>
<Grid>
<UserControl x:Name="usrControl"/>
<Button x:Name="btn" Content="Create" Click="btn_Click" IsEnabled = "{Binding validInput}"/>
</Grid>
</Window>
MainWindow C#:
using System;
using System.Windows;
using System.Windows.Controls;
namespace myClass {
public partial class MainWindow: Window {
UserControlViewModel view;
public MainWindow() {
InitializeComponent();
view = new UserControlViewModel();
DataContext = view;
}
}
I need to be able to check the contents of the text boxes in the UserControl from the MainWindow as my view is in the MainWindow, however the contents are inaccessible to me and it doesn't make sense to have the view in the UserControl. How should I go about solving this?
I've created a similar project. Mainly to do this, validate through your c# code. Basically
(i don't remember id its .content or .text to get the value)
if(txtbox1.Content == ---or--- (textbox1.Content).Equals(Whatever)){
----code---
}
else{
MessageBox.Show("Error")
}
instead of 'disabling' the button (which I don't think you can do) just make it so if invalid, the user knows or just doesn't do anything.
unrelated: if you are wanting a certain input instead of a blank textbox input, you could use this code to give a base if user leaves empty
private void txtbox1_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (txtbox1.Text.Equals("your origional text"))
{
Name_Text.Text = "";
}
}
private void txtbox1_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (Name_Text.Text.Equals(""))
{
Name_Text.Text = "your origional text";
}
}
hope this helps

How to change main window view from a pop up window

I'm trying to learn MVVM but am finding it a nightmare trying to understand how to correctly navigate between views in an application using MVVM. After some time researching and trying to understand different techniques I have come across an approach from Rachel Lim's blog. This technique uses a ViewModel for the application itself and keeps track of the application state such as the current page. I feel this would be a nice approach to follow for my application.
Now moving onto my problem..
What I want to achieve
I want an application that has a one main application view that will store a LoginView and a HomeView as DataTemplates and have a content control that sets the LoginView as the view displayed when the application is started. The LoginView will have a button that when pressed will open another window that has a button. When the button in the pop up window is pressed I want to change the view in the main application window from LoginView to the HomeView.
What I have so far
I have a set up the ApplicationView which works fine.
<Window x:Class="WPF_Navigation_Practice.Views.ApplicationView"
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:ignore="http://www.galasoft.ch/ignore"
xmlns:vm="clr-namespace:WPF_Navigation_Practice.ViewModels"
xmlns:views="clr-namespace:WPF_Navigation_Practice.Views"
mc:Ignorable="d ignore"
DataContext="{StaticResource ApplicationViewModel}">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:LoginViewModel}">
<views:LoginView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<views:HomeView />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</Grid>
</Window>
And have set up the ApplicationViewModel as follows. Setting the current page to the LoginViewModel.
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using WPF_Navigation_Practice.Interfaces;
namespace WPF_Navigation_Practice.ViewModels
{
/// <summary>
/// This class contains properties that a View can data bind to.
/// <para>
/// See http://www.galasoft.ch/mvvm
/// </para>
/// </summary>
public class ApplicationViewModel : ViewModelBase
{
#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion
public ApplicationViewModel()
{
// Add available pages
PageViewModels.Add(new LoginViewModel());
PageViewModels.Add(new HomeViewModel());
PageViewModels.Add(new CodeViewModel());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
}
#region Properties / Commands
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand<object>(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);
}
return _changePageCommand;
}
}
public List<IPageViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IPageViewModel>();
return _pageViewModels;
}
}
public IPageViewModel CurrentPageViewModel
{
get
{
return _currentPageViewModel;
}
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
RaisePropertyChanged("CurrentPageViewModel");
}
}
}
#endregion
#region Methods
private void ChangeViewModel(IPageViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
}
#endregion
}
}
When I run the application it will display my main Application window which displays the loginView which is a UserControl and is set as the currentPageViewModel with ContentPresenter.
When the button in the LoginView UserControl is clicked it will open another window. As per the image below.
Here is the XAML for that window.
<Window x:Class="WPF_Navigation_Practice.Views.CodeView"
x:Name="CodeWindow"
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:ignore="http://www.galasoft.ch/ignore"
xmlns:z="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:viewModels="clr-namespace:WPF_Navigation_Practice.ViewModels"
mc:Ignorable="d ignore"
d:DesignWidth="623.224" d:DesignHeight="381.269"
DataContext="{Binding CodeViewModel, Source={StaticResource ApplicationViewModel}}">
<Grid>
<Button Content="Ok"
HorizontalAlignment="Left"
Margin="235,166,0,0"
VerticalAlignment="Top"
Width="138"
FontSize="20"
Height="67"/>
<Label Content="Second Window" HorizontalAlignment="Left" Margin="166,56,0,0" VerticalAlignment="Top" FontSize="36"/>
</Grid>
My Problem
What I want to achieve is when the 'Ok' button in the secondView window is clicked, I want to change the currentPageViewModel in the ApplicationView Window from the LoginView to display the HomeView but am confused on how I would go about achieving this. Any help would be greatly appreciated.
I see that you are already using MVVMLight. There is a Messenger class which can help you here. Register to the messenger in your ApplicationViewModel Constructor and in the code handling the button click in CodeViewModel use Send to send a message. In the action you pass on to register change the viewmodels as you wish.
See http://www.mvvmlight.net/help/WP8/html/9fb9c53a-943a-11d7-9517-c550440c3664.htm
and Use MVVM Light's Messenger to Pass Values Between View Model
I don't have MVVMLight to write you a sample code. I've written a ViewModelMessenger from scratch and mine is like this:
public static void Register(string actionName, object registerer, Action<object, object> action)
{
var actionKey = new Tuple<string, object>(actionName, registerer);
if (!RegisteredActions.ContainsKey(actionKey))
{
RegisteredActions.Add(actionKey, action);
}
else
{
RegisteredActions[actionKey] = action;
}
}
Used like:
VMMessenger.Register("ChangeViewModel",this,ChangeViewModelAction)
and
public static void SendMessage(string messageName, object message, object sender)
{
var actionKeys = RegisteredActions.Keys.ToList();
foreach (Tuple<string, object> actionKey in actionKeys)
{
if (actionKey.Item1 == messageName)
{
Action<object, object> action;
if (RegisteredActions.TryGetValue(actionKey, out action))
{
action?.Invoke(message, sender);
}
}
}
}
Used like:
VMMessenger.SendMessage("ChangeViewModel","HomeViewModel",this);
and in ChangeViewModelAction you can check for ViewModel names and change the CurrentPageViewModel to one with a matching name.

C# Prism : Setting ViewModel property from a controller (MVVM)

The ViewModel:
public class ConnectionStatusViewModel : BindableBase
{
private string _txtConn;
public string TextConn
{
get { return _txtConn; }
set { SetProperty(ref _txtConn, value); }
}
}
The XAML:
<UserControl x:Class="k7Bot.Login.Views.ConnectionStatus"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://www.codeplex.com/prism"
prism:ViewModelLocator.AutoWireViewModel="True" Width="300">
<Grid x:Name="LayoutRoot">
<Label Grid.Row="1" Margin="10,0,10,0">connected:</Label>
<TextBlock Text="{Binding TextConn}" Grid.Row="1" Grid.Column="1" Margin="10,0,10,0" Height="22" />
</Grid>
</UserControl>
The View:
public partial class ConnectionStatus : UserControl
{
public ConnectionStatus()
{
InitializeComponent();
}
}
In another module, I have an event listener, that eventually runs this code:
ConnectionStatusViewModel viewModel = _connectionView.DataContext as ConnectionStatusViewModel;
if (viewModel != null)
{
viewModel.TextConn = "Testing 123";
}
The code runs but the TextConn is updated and does not display in the UI
Are you sure TextConn does not update? Because it can update but the display could not change. You should implement the INotifyPropertyChanged interface and after you make any changes to TextConn call the implemented OnPropertyChanged("TextConn"); or whatever you name the function. This will tell the UI that the value has changed and it needs to update.
The UserControl's DataContext gets its value when the UC is initialized. Then you get a copy of the DataContext, cast it to a view model object, and change the property. I don't believe that the UC gets its original DataContext updated in this scenario.
Probably you need to use a message mediator to communicated changes between different modules.
After some troubleshooting, this code works, the issue was that I was running this code:
ConnectionStatusViewModel viewModel = _connectionView.DataContext as ConnectionStatusViewModel;
if (viewModel != null)
{
viewModel.TextConn = "Testing 123";
}
before the view was actually activated. Silly, but maybe it will help someone down the line.

WPF ComboBox does not display content

I have a simple combo box on my xaml file:
<ComboBox Name="environmentComboBox" Grid.Column="1" Grid.Row="0" Margin="2"
SelectionChanged="environmentComboBox_SelectionChanged"
ItemsSource="{Binding Path=Test}"/>
Here is the code for its content:
private List<string> test = new List<string>(){"1", "2"};
public List<string> Test
{
get
{
return test;
}
set
{
test = value;
}
}
I tried to debug the application, the ComboBox does not show anything.
But when I checked if Test has content, it shows the two strings.
Have to set the views DataContext to the Model/Window containing the List<T>?
If not you need to tell the View what DataContext to use, below is a quick example of a WPF window, and setting the xamls DataContext to the code behind of the View.
Also its recommended to use ObservableCollection<T> when binding collections as adding and removing items will update the ComboBox automatically
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this; // set datacontext
}
private ObservableCollection<string> test = new ObservableCollection<string>() { "1", "2" };
public ObservableCollection<string> Test
{
get { return test; }
set { test = value; }
}
}
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ComboBox ItemsSource="{Binding Path=Test}"/>
</StackPanel>
</Window>

A Simple Wpf MVVM Binding Issue

I am trying my hands on WPF MVVM. I have written following code in XAML
<UserControl x:Class="Accounting.Menu"
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:Accounting"
mc:Ignorable="d"
d:DesignHeight="105" d:DesignWidth="300">
<UserControl.DataContext>
<local:MenuViewModel/>
</UserControl.DataContext>
<StackPanel>
<StackPanel>
<TextBlock Text="{Binding Path=MenuHeader}"/>
</StackPanel>
<ListBox ItemsSource="{Binding Path=MenuItems}" Height="70"/>
</StackPanel>
</UserControl>
I have got a MenuViewModel with properties MenuHeader and MenuItems. I get values in both the properties during runtime. Former is bound to text of TextBlock and latter to ItemSource of ListBox. But when I run the solution, TextBlock and ListBox are empty.
Edit: Code of ViewModel
public class MenuViewModel: ViewModelBase
{
AccountingDataClassesDataContext db;
private string _menuType;
public string MenuHeader { get; set; }
public ObservableCollection<string> MenuItems { get; set; }
public MenuViewModel()
{
}
public MenuViewModel(string menuType)
{
this._menuType = menuType;
db = new AccountingDataClassesDataContext();
if (menuType == "Vouchers")
{
var items = db.Vouchers.OrderBy(t => t.VoucherName).Select(v => v.VoucherName).ToList<string>();
if (items.Any())
{
MenuItems = new ObservableCollection<string>(items);
MenuHeader = "Vouchers";
}
}
else
{
System.Windows.MessageBox.Show("Menu not found");
}
}
}
Thanks in advance.
You are creating your ViewModel in the XAML using your ViewModel's default contructor which does nothing. All your population code is in the non-default contructor which is never called.
The more usual way is to create the ViewModel in code, and inject it into the view either explicitly using View.DataContext = ViewModel, or impllcitly using a DataTemplate.
I think you have to trigger the OnPropertyChanged event. I am not sure if you are using a MVVM library (since you inherit from ViewModelBase you might be using MVVM Light for example), there they wrap the OnPropertyChanged in the RaisePropertyChanged event handler.
Triggering the event will inform WPF to update the UI.
string m_MenuHeader;
public string MenuHeader
{
get
{
return m_MenuHeader;
}
set
{
m_MenuHeader=value; OnPropertyChanged("MenuHeader");
}
}

Categories