I made this minimalistic project to learn output and input with user control and it's working as intended. I want to ask, is this a good approach or is there something which is not necessary?
I also want to post this, because there is tons of post with specific user cases, but not one with a simple example to learn binding mechanics.
Main Window:
<Window x:Class="OutputFromUserControl.View.OutputFromUserControlWindow"
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:OutputFromUserControl.View"
xmlns:uc="clr-namespace:OutputFromUserControl.View.Controls"
xmlns:vm="clr-namespace:OutputFromUserControl.ViewModel"
mc:Ignorable="d"
Title="Output From User Control" Height="450" Width="800">
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
<StackPanel HorizontalAlignment="Left">
<Label Content="Form elements:"/>
<Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
<Grid HorizontalAlignment="Left" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Content="Name Input: " Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<Label Content="Surname Input: " Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<Label Content="Name Output from Control: " Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1"
Text="{Binding FullName}"
Width="200"
/>
</Grid>
</Border>
<Label Content="User Control:" Margin="0,10,0,0"/>
<Border CornerRadius="5" BorderBrush="Red" BorderThickness="1">
<uc:NameConcatControl x:Name="NameUC"
NameInput="{Binding NameInput}"
SurnameInput="{Binding SurnameInput}"
NameOutput="{Binding FullName, Mode=TwoWay}"
/>
</Border>
</StackPanel>
</Window>
MainVM:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
namespace OutputFromUserControl.ViewModel
{
public class MainVM : INotifyPropertyChanged
{
private string nameInput;
public string NameInput {
get { return nameInput; }
set
{
nameInput = value;
OnPropertyChanged(nameof(NameInput));
}
}
private string surnameInput;
public string SurnameInput {
get { return surnameInput; }
set {
surnameInput = value;
OnPropertyChanged(nameof(SurnameInput));
}
}
private string fullName;
public string FullName {
get { return fullName; }
set {
fullName = value;
OnPropertyChanged(nameof(FullName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Control xaml:
<UserControl x:Class="OutputFromUserControl.View.Controls.NameConcatControl"
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:OutputFromUserControl.View.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Content="Name Input: " Grid.Row="0" Grid.Column="0"/>
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput}"
x:Name="NameInputTextBlock"
/>
<Label Content="Surname Input: " Grid.Row="1" Grid.Column="0"/>
<TextBlock Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput}"
x:Name="SurnameInputTextBlock"
/>
<Label Content="Name Output: " Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1"
Text="{Binding NameOutput}"
x:Name="OutputNameTextBlock"
/>
</Grid>
</UserControl>
User control .cs:
using System.Windows;
using System.Windows.Controls;
namespace OutputFromUserControl.View.Controls
{
/// <summary>
/// Interaction logic for NameConcatControl.xaml
/// </summary>
public partial class NameConcatControl : UserControl
{
public string NameInput {
get { return (string)GetValue(NameInputProperty); }
set { SetValue(NameInputProperty, value); }
}
public static string defaultNameInput = "NameInput";
public static readonly DependencyProperty NameInputProperty =
DependencyProperty.Register("NameInput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultNameInput, SetNameOutput));
public string SurnameInput {
get { return (string)GetValue(SurnameInputProperty); }
set { SetValue(SurnameInputProperty, value); }
}
public static string defaultSurnameInput = "Surname Input";
public static readonly DependencyProperty SurnameInputProperty =
DependencyProperty.Register("SurnameInput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultSurnameInput, SetNameOutput));
public string NameOutput {
get { return (string)GetValue(NameOutputProperty); }
set { SetValue(NameOutputProperty, value); }
}
public static string defaultNameOutput = "Name Output";
public static readonly DependencyProperty NameOutputProperty =
DependencyProperty.Register("NameOutput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultNameOutput));
private static void SetNameOutput(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
NameConcatControl control = (NameConcatControl)d;
string nameInput = "";
string surnameInput = "";
if(e.Property.Name == "NameInput")
{
string newValue = (string)e.NewValue;
nameInput = string.IsNullOrEmpty(newValue) ? "" : newValue;
}
else
{
nameInput = string.IsNullOrEmpty(control.NameInputTextBlock.Text)
? ""
: control.NameInputTextBlock.Text;
}
if(e.Property.Name == "SurnameInput")
{
string newValue = (string)e.NewValue;
surnameInput = string.IsNullOrEmpty(newValue) ? "" : newValue;
}
else
{
surnameInput = string.IsNullOrEmpty(control.SurnameInputTextBlock.Text)
? ""
: control.SurnameInputTextBlock.Text;
}
string fullName = $"{nameInput} {surnameInput}";
control.OutputNameTextBlock.Text = fullName;
control.NameOutput = fullName;
}
public NameConcatControl()
{
InitializeComponent();
}
}
}
This question has a very wide answers. Different people with different approaches can use for their applications.
But we always follow one common formula.
Each view - will have its own view model. (Again in this approach, someone might say might not be true all the time).
From your code (xaml and code), below are my observations.
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
I generally don't like setting data context in xaml. Instead I prefer to set it on the code-behind (mostly from constructor)
Instead of creating a dependency properties in user control and bind the MainVM properties to the dependency properties of User control.
I prefer to do it this way.
I prefer to create a separate UserControlViewModel.cs and add required properties to it.
public class UserControlViewModel : INotifyPropertyChanged
{
private string nameInput;
public string NameInput {
get { return nameInput; }
set
{
nameInput = value;
OnPropertyChanged(nameof(NameInput));
}
}
private string surnameInput;
public string SurnameInput {
get { return surnameInput; }
set {
surnameInput = value;
OnPropertyChanged(nameof(SurnameInput));
}
}
private string fullName;
public string FullName {
get { return fullName; }
set {
fullName = value;
OnPropertyChanged(nameof(FullName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Then I prefer to add this as a property in MainVM.cs
public class MainVM : INotifyPropertyChanged
{
private UserControlViewModel _userControlViewModel;
public UserControlViewModel UserControlViewModel
{
get { return _userControlViewModel; }
set
{
_userControlViewModel = value;
OnPropertyChanged(nameof(UserControlViewModel));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
// Rest of your code
// You don't need existing properties any more here.
// If you want to access these properties from MainVM then use the UserControlViewModel property and access the members of it.
}
Then I prefer to set the data-context of my UserControl to this property like below in my MainWindow.xaml
<uc:NameConcatControl x:Name="NameUC" ="{Binding UserControlViewModel}" />
My usercontrol contorl binding's still remain same as the property names are same and we moved to UserControlViewModel.cs
Now you can remove all dependency properties from code behind of UserControl.xaml.cs
Note :- As I stated at the beginning of my answer, this question has wide area for answers and there are lot of possibilities to answer this question.
I hope I have tried to give you some inputs from my end. I guess this should give you some idea to develop rest..
You can try making those changes and let me know in case if you face any errors or binding issues.
Assuming you just want the full-name view to be something like "Surname, Name", you could actually remove the FullName property from your view model, and just use a MultiBinding (btw the StringFormat property can be used with both MultiBindings and regular Bindings, its pretty nifty if you aren't familiar with it).
As for the Labels, it's good to make a habit of using the simplest control required to get the job done, and in this case, TextBlocks would do just fine, since you don't appear to be using any of the extended functionality the Label offers (i.e., BorderBrush, Padding, ContentTemplate, etc.).
You don't generally need to create your own dependency properties in UserControl derived classes, since they are usually designed with a particular viewmodel in mind. They are more useful when the view is independent from the viewmodel, and the dependency properties serve as an api, through which other controls/viewmodels can interact with it.
<Window x:Class="OutputFromUserControl.View.OutputFromUserControlWindow"
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:OutputFromUserControl.View"
xmlns:uc="clr-namespace:OutputFromUserControl.View.Controls"
xmlns:vm="clr-namespace:OutputFromUserControl.ViewModel"
mc:Ignorable="d"
Title="Output From User Control" Height="450" Width="800">
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
<StackPanel HorizontalAlignment="Left">
<Label Content="Form elements:"/>
<Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
<Grid HorizontalAlignment="Left" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Name Input:" Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<TextBlock Text="Surname Input:" Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<TextBlock Text="Name Output from Control:" Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1" Width="200">
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="SurnameInput"/>
<Binding Path="NameInput"/>
</MultiBinding>
</TextBlock>
</Grid>
</Border>
<Label Content="User Control:" Margin="0,10,0,0"/>
<Border CornerRadius="5" BorderBrush="Red" BorderThickness="1">
<uc:NameConcatControl x:Name="NameUC"
NameInput="{Binding NameInput}"
SurnameInput="{Binding SurnameInput}"
NameOutput="{Binding FullName}"
/>
</Border>
</StackPanel>
Related
I have two views for my application. Looking at the first View, UpdaterMainView, you can see that I create a Grid with two columns. The left column is split even further, into 5 rows. What I want to do is fill the five rows from a list of objects that are stored in the UpdaterMainViewModel, TaskList. I created a custom User Control called TaskView which is the layout of how I want to display the information of the list, a button, and a textbox.
When I instantiate the UpdaterMainViewModel, my application scans a directory for a few things and creates a list of tasks to complete. There are only five total possible tasks, hence why I created five rows. If the conditions aren't met and Task 2 doesn't need to be run, I don't want to show the UserControl and the one below it, should move up. I don't want to use the code-behind unless it can still meet the guidelines of MVVM.
For testing, I added <local:TaskView Grid.Column="0" Grid.Row="0"/> to the UpdaterMainView and created a default constructor. But, what I need, is to add TaskView for each item in TaskList. To get this to work, I also have to create the UserControls with a different constructor. When you use DataContext, it uses a parameterless constructor. I somehow need to use the constructor with the enum parameter to fill the grid with each appropriate TaskView.
UpdaterMainView:
<UserControl x:Class="POSUpdaterGUI.View.UpdaterMainView"
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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local1:UpdaterMainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<local:TaskView Grid.Column="0" Grid.Row="0"/>
<local:TaskView Grid.Column="0" Grid.Row="1"/>
<local:TaskView Grid.Column="0" Grid.Row="2"/>
<local:TaskView Grid.Column="0" Grid.Row="3"/>
</Grid>
<TextBox Grid.Column="1" Margin="10" TextWrapping="Wrap" Text="{Binding OutputText}"/>
</Grid>
</UserControl>
UpdaterMainViewModel:
using POSUpdaterLibrary;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace POSUpdaterGUI.ViewModel
{
public class UpdaterMainViewModel : INotifyPropertyChanged
{
private string outputText;
public string OutputText
{
get => outputText;
set
{
outputText = value;
NotifyPropertyChanged("OutputText");
}
}
public UpdateTaskList TaskList { get; set; }
public UpdaterMainViewModel()
{
Log("Application Starting.");
TaskList = new UpdateTaskList();
TaskList.LoggerEvent += TaskList_LoggerEvent;
TaskList.GetList(AppContext.BaseDirectory);
}
private void TaskList_LoggerEvent(object sender, LoggerEventArgs e)
{
Log(e.LogString);
}
private void Log(string message)
{
if (OutputText == null)
{
OutputText = DateTime.Now.ToString() + " - " + message;
return;
}
OutputText += "\n" + DateTime.Now.ToString() + " - " + message;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
TaskView:
<UserControl x:Class="POSUpdaterGUI.View.TaskView"
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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="500">
<UserControl.DataContext>
<local1:TaskViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center" Height="50" Width="50" Command="{Binding RunTaskCommand}">
<Image Source="{Binding StatusImage}"/>
</Button>
<Label Grid.Column="1" Content="{Binding TaskName}" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Center"/>
</Grid>
TaskViewModel:
using POSUpdaterLibrary;
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace POSUpdaterGUI.ViewModel
{
class TaskViewModel : INotifyPropertyChanged
{
public string TaskName { get; set; }
public ICommand RunTaskCommand { get; set; }
public string StatusImage { get; set; }
public TaskViewModel()
{
TaskName = "undefined";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
}
public TaskViewModel(UpdaterConstants.TaskType taskType)
{
switch (taskType)
{
case UpdaterConstants.TaskType.KillProcesses:
TaskName = "Processes Killed";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.FileCopy:
TaskName = "Files Copied";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.DatabaseUpdates:
TaskName = "Database Updated";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.COMDLLsRegistered:
TaskName = "COM DLLs Registered";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.FileCleanup:
TaskName = "Files Cleaned";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
default:
break;
}
}
public enum Status
{
START,
COMPLETE,
FAILED,
WAIT
}
internal const string STARTIMAGE = "/data/start.png";
internal const string COMPLETEIMAGE = "/data/complete.png";
internal const string FAILEDIMAGE = "/data/failed.png";
internal const string WAITIMAGE = "/data/wait.png";
public event PropertyChangedEventHandler PropertyChanged;
private void DefaultMethod(object obj)
{
// Shouldn't be shown. Just a hold-over
throw new NotImplementedException();
}
}
}
The UpdaterMainView is the main view of the application and is always showing. To prove this, here is my MainWindow.xaml:
<Window
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:POSUpdaterGUI"
xmlns:View="clr-namespace:POSUpdaterGUI.View" x:Class="POSUpdaterGUI.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<View:UpdaterMainView/>
</Grid>
Thanks #Clemens for pointing me in the right direction! I did not need a separate View. I did need to use Data Templating.
Here is what I ended up with for the UpdaterMainView:
<UserControl x:Class="POSUpdaterGUI.View.UpdaterMainView"
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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local1:UpdaterMainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" Margin="10" ItemsSource="{Binding TaskList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center" Height="50" Width="50" Command="{Binding Command}">
<Image Source="{Binding StatusImage}"/>
</Button>
<Label Grid.Column="1" Content="{Binding TaskName}" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Grid.Column="1" Margin="10" TextWrapping="Wrap" Text="{Binding OutputText}"/>
</Grid>
</UserControl>
Here is what I ended up with for the UpdaterMainViewModel:
using POSUpdaterGUI.Models;
using POSUpdaterLibrary;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace POSUpdaterGUI.ViewModel
{
public class UpdaterMainViewModel : INotifyPropertyChanged
{
private UpdaterLogger _logger;
private string outputText;
public string OutputText
{
get => outputText;
set
{
outputText = value;
NotifyPropertyChanged("OutputText");
}
}
private List<UpdateTaskWrapper> taskList;
public List<UpdateTaskWrapper> TaskList
{
get => taskList;
set
{
taskList = value;
NotifyPropertyChanged("TaskList");
}
}
public UpdaterMainViewModel()
{
_logger = UpdaterLogger.Instance;
_logger.LoggerEvent += Logger_LoggerEvent;
Log("Application Starting.");
TaskList = GetTaskList();
}
private void Logger_LoggerEvent(object sender, LoggerEventArgs e)
{
Log(e.LogString);
}
private List<UpdateTaskWrapper> GetTaskList()
{
UpdateTaskList list = new UpdateTaskList();
var myList = list.GetList(AppContext.BaseDirectory);
// Create one out of the Wrapper class.
List<UpdateTaskWrapper> wrappedList = new List<UpdateTaskWrapper>();
foreach (var task in myList)
{
wrappedList.Add(new UpdateTaskWrapper(task));
}
return wrappedList;
}
private void Log(string message)
{
if (OutputText == null)
{
OutputText = DateTime.Now.ToString() + " - " + message;
return;
}
OutputText += "\n" + DateTime.Now.ToString() + " - " + message;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
The most important parts were that I removed the TaskView, Added Datatemplating (between the tags), and changed the List from a custom UpdaterTaskList to List.
This is my first time writing here. I though my first question would by more complex but I am tired of searching for an answer.
I just started with WPF (MVVM), here it goes:
I have 3 user Controls in a Page, the three of them are the same class of Control. The important thing here is that in the first TextBox, when it lost focus, it calls a method to calculate the last TextBox.Text.
<UserControl x:Class="ASSEMBLY.View.UserControls.EtiquetaDinamicaUserControl"
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:ASSEMBLY.View.UserControls"
x:Name="userControl"
mc:Ignorable="d"
Background="Orange" BorderThickness="0" >
<DockPanel VerticalAlignment="Stretch">
<Grid Background="green">
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Viewbox Grid.Column="0" VerticalAlignment="Stretch" Stretch="Fill" >
<Label Grid.Column="0" Content="{Binding NombreEtiqueta, ElementName=userControl, Mode=TwoWay}" HorizontalAlignment="Stretch" BorderThickness="0" Margin="0,0,5,0"
Background="White" VerticalAlignment="Stretch" />
</Viewbox>
<Viewbox Grid.Column="1" VerticalAlignment="Stretch" Stretch="Fill" >
<TextBox Grid.Column="1" MinWidth="30" BorderThickness="0" Margin="0,0,5,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Left" LostFocus="IdChanged" Loaded="IdChanged"
Text="{Binding IdValue, ElementName=userControl, Mode=TwoWay}"
/>
</Viewbox>
<Viewbox Grid.Column="2" VerticalAlignment="Stretch" Stretch="Fill" >
<TextBox Grid.Row="0" HorizontalAlignment="Stretch" IsEnabled="True" MinWidth="100" BorderThickness="0"
TextAlignment="Left" VerticalAlignment="Stretch"
Text="{Binding Path= Descripcion, ElementName=userControl, Mode=TwoWay}">
</TextBox>
</Viewbox>
</Grid>
</DockPanel>
I have 3 times the same control in the Page, now I need that when the UserControl2 fires LostFocus also fire the LostFocus of the usercontrol3.
<controls:EtiquetaDinamicaUserControl Grid.Row="0" Grid.Column="0" Margin="5,2,0,0" IdValue="{Binding Codempresa1, Mode=TwoWay}" NombreEtiqueta="TEST1"/>
<controls:EtiquetaDinamicaUserControl x:Name="UserControl2" Grid.Row="1" Grid.Column="0" Margin="5,2,0,0" IdValue="{Binding Codempresa2, Mode=TwoWay}" NombreEtiqueta="TEST2"/>
<controls:EtiquetaDinamicaUserControl x:Name="UserControl3" Grid.Row="2" Grid.Column="0" Margin="5,2,0,2" IdValue="{Binding Codempresa3, Mode=TwoWay}" NombreEtiqueta="TEST3"/>
I searched everywhere for something similar, but I found eventrigger (not working because I am not changing a property), interaction (no success trying it).
It works if I set the TextChangedproperty instead of Lostfocus because everything it's bound, but I don't want to be calculating the second textbox every character the user input.
Sorry for my english mistakes and thank you for your help
EDITED. IN ORDER TO HELP I COPY THE CODE BEHIND OF THE USERCONTROL AND THE MVVM PART OF THE VIEW.
USERCONTROL CODE BEHIND:
public static readonly DependencyProperty nombreEtiqueta =
DependencyProperty.Register("NombreEtiqueta", typeof(string), typeof(EtiquetaDinamicaUserControl), new
PropertyMetadata("DEF"));
public string NombreEtiqueta
{
get { return (string)GetValue(nombreEtiqueta); }
set { SetValue(nombreEtiqueta, value); }
}
public static readonly DependencyProperty SetDescripcionProperty =
DependencyProperty.Register("Descripcion", typeof(string), typeof(EtiquetaDinamicaUserControl), new
PropertyMetadata("SIN ASIGNAR"));
public string Descripcion
{
get { return (string)GetValue(SetDescripcionProperty); }
set { SetValue(SetDescripcionProperty, value); }
}
public static DependencyProperty SetIdValueProperty =
DependencyProperty.Register("IdValue", typeof(string), typeof(EtiquetaDinamicaUserControl), new
PropertyMetadata("0"));
public string IdValue
{
get { return (string)GetValue(SetIdValueProperty); }
set { SetValue(SetIdValueProperty, value);
}
}
#region Evento al perder foco IdValue
private void IdChanged(object sender, RoutedEventArgs e)
{
try
{
int tmp_id = Convert.ToInt32(((TextBox)e.Source).Text);
if (tmp_id != 0)
{
Descripcion = tmp_id.ToString();
}
}
catch
{
Descripcion = "SOLO NUMEROS";
}
}
#endregion
MVVM from the View
class PaginaReservaViewModel:ViewModelBase
{
Reserva reserva;
#region Atributos Agencias
private int codempresa1;
private int codempresa2;
private int codempresa3;
#endregion
#region Get/Set Agencias
public int Codempresa1 { get { return codempresa1; } set { codempresa1 = value; } }
public int Codempresa2
{
get { return codempresa2; }
set
{
codempresa2 = value;
if (codempresa3 == 0)
{
codempresa3 = codempresa2;
OnPropertyChanged("Codempresa3");
}
}
}
public int Codempresa3 {
get { return codempresa3; }
set {
codempresa3 = value; } }
Can you use UpdateSourceTrigger="LostFocus" in UserControl' TextBox's Text binding? so it wont fire on every key input. Also please try to implement this all with MVVM (Without hooking events in Code Behind), then you will have much control over the operation.
My Suggestion:
In ViewModel implement a way to modify the other values, when changing Codempresa1. You can include a method to calculate them and call the method in set method of Codempressa1 or you can include the method in PropertyChanged handler.
I have a WPF application and just embarked in learning MVVM pattern.
My goal is that in my application the main window has a button. When this button is clicked, another window (or user control) will appear on top of the main window.
This is the code of MainWindow.xaml
<Window x:Class="SmartPole1080.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utilities="clr-namespace:SoltaLabs.Avalon.Core.Utilities;assembly=SoltaLabs.Avalon.Core"
xmlns:userControls="clr-namespace:SoltaLabs.Avalon.View.Core.UserControls;assembly=SoltaLabs.Avalon.View.Core"
xmlns:controls="clr-namespace:WpfKb.Controls;assembly=SmartPole.WpfKb"
xmlns:wpf="clr-namespace:WebEye.Controls.Wpf;assembly=WebEye.Controls.Wpf.WebCameraControl"
xmlns:view="clr-namespace:SmartPole.View;assembly=SmartPole.View"
xmlns:view1="clr-namespace:SmartPole1080.View"
xmlns:behaviors="clr-namespace:SoltaLabs.Avalon.Core.Behaviors;assembly=SoltaLabs.Avalon.Core"
Title="Smart Pole"
WindowStartupLocation="CenterScreen"
Name="mainWindow"
behaviors:IdleBehavior.IsAutoReset="True" WindowState="Maximized" WindowStyle="None">
<Canvas Background="DarkGray">
<!--Main Grid-->
<Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Background="Black">
<Grid Background="#253341">
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="264"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
Command="{Binding ShowEmergencyPanel}">
<Image Source="{StaticResource EmergencyImg}" />
</Button>
</Grid>
<!--Emergency Window Dialog-->
<Grid Name="EmergencyPanel">
<view1:EmergencyInfo x:Name="EmergencyInfoPanel"/>
</Grid>
</Grid>
</StackPanel>
</Grid>
<!--Main Grid-->
</Canvas>
This is the other window (user control - EmergencyInfo.xaml)
<UserControl x:Class="SmartPole1080.View.EmergencyInfo"
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:SmartPole1080.View"
mc:Ignorable="d"
d:DesignHeight="1920" d:DesignWidth="1050"
x:Name="EmergencyInfoPanel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderBrush="White" Background="White">
<Button Background="White" BorderThickness="0" FontWeight="Bold" Foreground="Red"
HorizontalAlignment="Right" FontSize="25" Margin="0,0,15,0"
Command="{Binding HideEmergencyPanel}">
close X
</Button>
</Border>
<Image Grid.Row="1" Source="{StaticResource EdenParkInfoImg}" HorizontalAlignment="Left" />
</Grid>
I want to implement this behavior using an MVVM pattern. I have set the binding ShowEmergencyPanel in button EmergencyButton to show EmergencyInfo when this button is click.
Any help is greatly appreciated.
Why dont you make navigation, something like this. Make section for conetent that will be injected, and whatever type of object you are expecting put it in Windows.Resources in DataTemplate.
In main windo xaml
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type other:OtherViewModel}">
<other:OtherView />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="Navigation">
<StackPanel Orientation="Horizontal">
<Button x:Name="HomeView"
Content="Home"
Margin="5"
Command="{Binding NavigationCommand}"
CommandParameter="home" />
<Button x:Name="Menu"
Content="OtherView"
Margin="5"
Command="{Binding NavigationCommand}"
CommandParameter="Other" />
</StackPanel>
</Grid>
<Grid x:Name="MainContent"
Grid.Row="1">
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
</Grid>
MainWindowViewModel can look something like this.
public class MainWindowViewModel : INotifyPropertyChanged
{
private OtherViewModel otherVM;
private HomeViewModel homeVM;
public DelegateCommand<string> NavigationCommand { get; private set; }
public MainWindowViewModel()
{
otherVM = new OtherViewModel();
homeVM = new HomeViewModel();
// Setting default: homeViewModela.
CurrentViewModel = homeVM;
NavigationCommand = new DelegateCommand<string>(OnNavigate);
}
private void OnNavigate(string navPath)
{
switch (navPath)
{
case "other":
CurrentViewModel = otherVM;
break;
case "home":
CurrentViewModel = homeVM;
break;
}
}
private object _currentViewModel;
public object CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel != value)
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new propertyChangedEventArgs(propertyName));
}
#endregion
}
Where DelegateCommand you can make yours,check how to make RelayCommand(and generic one) or use PRISM that have it's own DelegateCommand. But if you want to use PRISM, it allready has navigation to regions, that can solve many problems. Check videos from Brian Lagunas.
EDIT:
This is to show/hide grid. In your mainWindow where you set that EmergencyInfo u can show/hide that grid this way.
in your MainViewViewModel make a bool property IsVisible
private bool _isVisible;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
OnPropertyChanged();
}
}
in your MainView.Resources set key to BooleanToVisibilityConverter
something like:
<BooleanToVisibilityConverter x:Key="Show"/>
and your grid that you want to show/hide set visibility:
<Grid x:Name="SomeGridName"
Grid.Row="1"
Grid.Colum="1"
Visibility="{Binding IsVisible,Converter={StaticResource Show}}">
And finally set that IsVisible property to some ToggleButton, just to switch between true/false
<ToggleButton IsChecked="{Binding IsVisible}"
Content="ShowGrid" />
This way, you show/hide that grid part based on IsVisible, and you control that visibility onClick. Hope that helps.
Inside your Window xaml:
<ContentPresenter Content="{Binding Control}"></ContentPresenter>
In this way, your ViewModel must contain a Control property.
Add your ViewModel to DataContext of Window.
(For example in window constructor, this.Datacontext = new ViewModel();)
Or another way with interfaces:
public interface IWindowView
{
IUserControlKeeper ViewModel { get; set; }
}
public interface IUserControlKeeper
{
UserControl Control { get; set; }
}
public partial class YourWindow : IViewWindow
{
public YourWindow()
{
InitializeComponent();
}
public IUserControlKeeper ViewModel
{
get
{
return (IUserControlKeeper)DataContext;
}
set
{
DataContext = value;
}
}
}
(In this way, initialize your window where you want to use. Service?)
private IViewWindow _window;
private IViewWindow Window //or public...
{
get{
if(_window==null)
{
_window = new YourWindow();
_window.ViewModel = new YourViewModel();
}
return _window;
}
}
Open your window with one of your UserControls:
void ShowWindowWithControl(object ControlView, INotifyPropertyChanged ControlViewModel, bool ShowAsDialog)
{
if(ControlView is UserControl)
{ //with interface: Window.ViewModel.Control = ...
(Window.DataContext as YourViewModel).Control = (UserControl)ControlView;
if (ControlViewModel != null)
(Window.DataContext as YourViewModel).Control.DataContext = ControlViewModel;
if (ShowAsDialog) //with interface use cast to call the show...
Window.ShowDialog();
else
Window.Show();
}
}
What you can do is, place the Emergency usercontrol inside MainGrid and control its visibility through Model property.
<Canvas Background="DarkGray">
<!--Main Grid-->
<Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Background="Black">
<Grid Background="#253341">
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="264"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
Command="{Binding ShowEmergencyPanel}">
<Image Source="{StaticResource EmergencyImg}" />
</Button>
</Grid>
</Grid>
</StackPanel>
<Grid Name="EmergencyPanel">
<view1:EmergencyInfo x:Name="EmergencyInfoPanel" Visibility={Binding IsEmergencyPanelVisible,Converter={StaticResource BoolToVisibilityConverter}} DataContext={Binding} />
</Grid>
</Grid>
<!--Main Grid-->
</Canvas>
By setting proper background to Grid that holding usercontrol, you can achieve modal window like effect.
And you need to have IsEmergencyPanelVisible(default value is false) is your Model, and this property should be changed to true on your button click command.
Note : I guess you are familiar with converters, so i included Converters in my solution.
I have created a view model which has a single property for a student model, that I am then binding to a control in my XAML. But nothing is appearing when I execute the application.
I am setting data context in my app.xaml.cs as follows:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Registrationformusinemvvm.MainWindow window = new MainWindow();
VMUser VM = new VMUser();
window.DataContext = VM;
window.Show();
}
Why is the binding not working?
This is my view model:
public class VMUser:BaseClass
{
private student _currentStudent;
public student CurrentStudent
{
get { return _currentStudent; }
set {
_currentStudent = value;
OnPropertyChanged("CurrentStudent");
}
}
}
My Student model class:
public class student:BaseClass
{
private string name="sumit";
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
private int rollNum;
public int RollNum
{
get { return rollNum; }
set { rollNum = value;OnPropertyChanged("RollNum"); }
}
private int phNum;
public int PhNum
{
get { return phNum; }
set { phNum = value;OnPropertyChanged("PhNum"); }
}
private string sub;
public string Sub
{
get { return sub; }
set { sub = value;OnPropertyChanged("Sub"); }
}
}
My XAML:
<Window x:Class="Registrationformusinemvvm.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:Registrationformusinemvvm"
xmlns:vm="clr-namespace:Registrationformusinemvvm.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<!--<Window.DataContext>
<vm:VMUser/>
</Window.DataContext>-->
<Window.Resources>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="50"/>
<RowDefinition Height="50"/>
<RowDefinition Height="50"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Name" Grid.Column="0" Grid.Row="0" FontSize="14"
FontWeight="Bold" VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<TextBlock Text="Roll Number" Grid.Column="0" Grid.Row="1" FontSize="14"
FontWeight="Bold" VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<TextBlock Text="Subject" Grid.Column="0" Grid.Row="2" FontSize="14"
FontWeight="Bold" VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<TextBlock Text="Phone Number" Grid.Column="0" Grid.Row="3"
FontSize="14" FontWeight="Bold" VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<TextBox Name="tbName" Text="{Binding CurrentStudent.Name,Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="0"
Width="120" Height="30" HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBox Name="tbRollnum" Text="{Binding CurrentStudent.RollNum}"
Grid.Column="1" Grid.Row="1" Width="120" Height="30"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Name="tbSub" Text="{Binding CurrentStudent.Sub}"
Grid.Column="1" Grid.Row="2" Width="120" Height="30"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Name="tbPh" Text="{Binding CurrentStudent.PhNum}"
Grid.Column="1" Grid.Row="3" Width="120" Height="30"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Button Name="tbSubmit" Content="Submit" Grid.ColumnSpan="3"
Grid.Row="4" Height="30" Width="100" HorizontalAlignment="Center"/>
</Grid>
</Window>
My guess is that your binding isn't working because your _currentStudent is null by default. Initialize your _currentStudent if null.
public student CurrentStudent
{
get { return _currentStudent = (_currentStudent ?? new student()); }
set
{
_currentStudent = value; OnPropertyChanged("CurrentStudent");
}
}
You need to add OnPropertyChanged in your model class.
void OnPropertyChanged(string prop)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
public event PropertyChangedEventHandler PropertyChanged;
As per your above code you cant assigned the value to CurrentStudent Property so
can you please check do you have the value to CurrentStudent property.
Thank you for your question
Remove StartupUri="YourXamlFile.xaml" from App.Xaml
I've created an example based on MVVM
Main window XAML:
<Window x:Class="LearnMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LearnMVVM"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<ObjectDataProvider x:Key="operationTypeEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:OperationType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<DataTemplate DataType="{x:Type local:SomeUserControlViewModel}">
<local:SomeUserControl />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="25"/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Column="0" Grid.Row="0" Margin="2" Text="{Binding Path=A, Mode=TwoWay}"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="+" TextAlignment="Center" VerticalAlignment="Center" Height="16" Margin="0,4,0,5"/>
<TextBox Grid.Column="2" Grid.Row="0" Margin="2" Text="{Binding Path=B, Mode=TwoWay}"/>
<Button Grid.Column="3" Grid.Row="0" Margin="2" Content="Посчитать" Command="{Binding ClickCommand}"/>
<TextBox Grid.Column="4" Grid.Row="0" Margin="2" IsReadOnly="True" Text="{Binding Path=Summa, Mode=TwoWay}"/>
<ComboBox Grid.Column="2" Grid.Row="1" SelectedItem="{Binding Path=SomeUserControl.Operation, Mode=TwoWay}" ItemsSource="{Binding Source={StaticResource operationTypeEnum}}" />
<ContentControl Grid.Column="2" Grid.Row="2" BorderThickness="3" BorderBrush="Black" Content="{Binding Path=SomeUserControl}" />
</Grid>
</Window>
XAML of the SomeUserControl:
<UserControl x:Class="LearnMVVM.SomeUserControl"
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:learnMvvm="clr-namespace:LearnMVVM"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<learnMvvm:SomeUserControlViewModel />
</UserControl.DataContext>
<DockPanel>
<TextBox DockPanel.Dock="Top" Margin="10" Text="{Binding Path=A, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Label DockPanel.Dock="Top" Content="{Binding Path=Operation, diag:PresentationTraceSources.TraceLevel=High}" />
<TextBox DockPanel.Dock="Top" Margin="10" Text="{Binding Path=B, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Button DockPanel.Dock="Top" Content="=" Margin="20" Command="{Binding CalculateOperationComamnd, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Label DockPanel.Dock="Top" Margin="10" Content="{Binding Path=Result, diag:PresentationTraceSources.TraceLevel=High}" />
</DockPanel>
</UserControl>
ViewModel of the SomeCustomUserControl:
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace LearnMVVM
{
public enum OperationType
{
Sum,
Sub,
Div,
Mul
}
public class SomeUserControlViewModel : INotifyPropertyChanged
{
public double A { get; set; }
public double B { get; set; }
//Команды
private ICommand calculateOperationCommand;
public ICommand CalculateOperationComamnd
{
get
{
return calculateOperationCommand;
}
set
{
if (calculateOperationCommand != value)
{
calculateOperationCommand = value;
OnPropertyChanged("CalculateOperationComamnd");
}
}
}
private OperationType operation;
public OperationType Operation
{
get
{
return operation;
}
set
{
if (operation != value)
{
operation = value;
switch (operation)
{
case OperationType.Sum:
CalculateOperationComamnd = new RelayCommand(arg => OperationSum());
break;
case OperationType.Sub:
CalculateOperationComamnd = new RelayCommand(arg => OperationSub());
break;
case OperationType.Div:
CalculateOperationComamnd = new RelayCommand(arg => OperationDiv());
break;
case OperationType.Mul:
CalculateOperationComamnd = new RelayCommand(arg => OperationMul());
break;
}
OnPropertyChanged("Operation");
}
}
}
private void OperationSum()
{
Result = A + B;
}
private void OperationSub()
{
Result = A - B;
}
private void OperationDiv()
{
Result = A/B;
}
private void OperationMul()
{
Result = A*B;
}
private double result;
public double Result
{
get { return result; }
set
{
if (Math.Abs(result - value) > 0.0001)
{
result = value;
OnPropertyChanged("Result");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Problem: the custom control does not change, when i'm changing selected item from the combo box and the "calculate button has no effect.
Actually all properties within of SomeCustomControlViewModel are updated as expected, but there is no effect in the main windows.
Have i missed something?
Operation is not a property of SomeUserControl. It is a property of SomeUserControl's viewmodel -- reachable as the control's DataContext. Try binding ComboBox.SelectedItem like so:
SelectedItem="{Binding Path=SomeUserControl.DataContext.Operation, Mode=TwoWay}"
The change is that I added DataContext to the path.
This is why you don't use viewmodels with custom controls, if you really want to use them as controls. You write a control class deriving from Control, and give it dependency properties. Operation should be a dependency property of a class derived from Control, not a notifying property on a viewmodel. Then you define UI for it by applying a ControlTemplate via a default Style.
What you've got here is really a child viewmodel. With that type of arrangement, ordinarily the parent viewmodel would provide an instance of the child viewmodel, and bind it to the child control itself. Then anybody who wanted to use a property of the child viewmodel would bind ChildVM.WhateverProperty.