I want to develop dialog for editing objects that make use of polymorphism. Currently I'm using this pattern:
MyObject.cs:
using System;
namespace WpfApplication3
{
public class MyObject
{
public string Title { get; set; }
public MySettings Settings { get; set; }
}
public abstract class MySettings
{
public abstract string GetSettingsString();
}
public class MyBoolSettings : MySettings
{
public bool BoolSetting { get; set; }
public override string GetSettingsString()
{
return "BoolSetting = " + BoolSetting;
}
}
public class MyStringSettings : MySettings
{
public string StringSetting { get; set; }
public override string GetSettingsString()
{
return "StringSetting = " + StringSetting;
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication3.EditMyObjectDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="EditMyObjectDialog" Height="350" Width="350">
<StackPanel Margin="20">
<TextBlock Text="Title" />
<TextBox Name="txtTitle" />
<RadioButton Name="rdBoolSettings" Content="BoolSettings" IsChecked="True" Margin="0, 20, 0, 0" />
<CheckBox Name="chBool" Content="True" Margin="20, 0, 0, 20" />
<RadioButton Name="rdStringSettings" Content="StringSettings" />
<TextBox Name="txtString" Margin="20, 0, 0, 20"/>
<Button Content="OK" Click="OK_click" />
<Button Content="Cancel" Click="Cancel_click" Margin="0, 10" />
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace WpfApplication3
{
public partial class EditMyObjectDialog : Window
{
public MyObject Result { get; set; }
public EditMyObjectDialog(MyObject objectToEdit)
{
InitializeComponent();
txtTitle.Text = objectToEdit.Title;
if (objectToEdit.Settings is MyBoolSettings)
{
rdBoolSettings.IsChecked = true;
chBool.IsChecked = (objectToEdit.Settings as MyBoolSettings).BoolSetting;
}
if (objectToEdit.Settings is MyStringSettings)
{
rdBoolSettings.IsChecked = true;
txtString.Text = (objectToEdit.Settings as MyStringSettings).StringSetting;
}
}
private void OK_click(object sender, RoutedEventArgs e)
{
Result = new MyObject() { Title = txtTitle.Text };
if (rdBoolSettings.IsChecked == true)
Result.Settings = new MyBoolSettings() { BoolSetting = chBool.IsChecked == true };
if (rdStringSettings.IsChecked == true)
Result.Settings = new MyStringSettings() { StringSetting = txtString.Text };
DialogResult = true;
}
private void Cancel_click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
}
}
ExternalCode:
var f = new EditMyObjectDialog(myObject);
if (f.ShowDialog() == true)
myObject = f.Result;
I belive there is much better design pattern that uses data binding etc. So basically I have two questions.
How to make data binding not to
modify object until user hits 'OK'?
How to correctly handle 'Settings'
property? What to do when user
switches setting's type?
What I believe you're looking for is a combination of DataBinding and DataTemplating. DataTemplating will allow you to define different visual elements for different business objects (in this case MyBooleanSettings and MyStringSettings. DataBinding will allow the visual elements to update and be updated my the data in the business objects.
Example (xaml):
<Window DataContext={Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<DataTemplate DataType={x:Type local:MyObject}">
<TextBlock Text={Binding Title}" />
<ContentPresenter Content="{Binding Settings}" />
</DataTemplate>
<DataTemplate DataType={x:Type local:MyObject}">
<TextBox Text={Binding
</DataTemplate>
<DataTemplate DataType={x:Type local:MyBoolSettings}>
<CheckBox IsChecked="{Binding BoolSetting}" />
</DataTemplate>
<DataTemplate DataType={x:Type local:MyStringSettings}>
<TextBox Text="{Binding StringSetting}" />
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding ObjectToEdit}" />
</Window>
Then in the code behind define:
public MyObject ObjectToEdit { get; set; }
Finally update your objects:
public class MySettings : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(sting s)
{
if(PropertyChanged != null)
{
PropertyChanged(s);
}
}
}
public class BoolSettings : MySettings
{
bool _value;
bool BoolSetting
{
get { return _value; }
set
{
if(_value != value)
{
_value = value;
OnPropertyChanged("BoolSetting");
}
}
}
}
If however you really need to control when the view and object sync you should use the UpdateSourceTrigger property on the corresponding bindings.
If you want some additional reading I recommend: http://msdn.microsoft.com/en-us/library/ms752347.aspx
DataBinding is Simple . You can create an instance of MyObject and assign it to the DataContext property of the Form.
this.DataContext=MyObject;
And define binding for individual elements.
<TextBox Name="txtTitle" Text="{Binding Path=Title,Mode=TwoWay }" />
Setting mode as two way will affect the object as you make change in UI. One way will show the values.
How to make data binding not to modify object until user hits 'OK'?
Create a copy of the MyObject instance. In the Result property get method, return copy if user hit cancel (return unchanged copy) or if user hit OK, return the changed MyObject instance.
How to correctly handle 'Settings' property? What to do when user switches setting's type?
Whats the problem?
Related
I’m learning C# and building a UI that reads and writes integers to an XML config file. The UI uses a variety of custom user controls. I have a 3 radiobutton user control that binds to a single int variable (control returns 0,1,2). The control uses an event to trigger the update. It looks at the 3 isChecked properties to determine the new int value. But I don’t know how to update the original binding value from the code behind. It's once removed so to speak because there are two binds..one in the main window and one in the user control. As a beginner am lost at this point. BTW reading the int value into the 3 radiobuttons is working using a converter.
here is the user control xaml.cs...
namespace btsRV7config.controls
{
public partial class ui3X : UserControl
{
public ui3X()
{
InitializeComponent();
}
void _event(object sender, RoutedEventArgs e)
{
int newValue = 0;
if (rdbttn1.IsChecked == true) { newValue = 0; }
else if (rdbttn2.IsChecked == true) { newValue = 1; }
else if (rdbttn3.IsChecked == true) { newValue = 2; }
txtb.Text = newValue.ToString(); //tempRemove
// !!! assign newValue to Binding Source !!!
//---------------------------------------------
uiBinding1 = newValue;
BindingOperations.GetBindingExpression(rdbttn1, RadioButton.IsCheckedProperty).UpdateSource();
//---------------------------------------------
}
public static readonly DependencyProperty uiBinding1Property = DependencyProperty.Register("uiBinding1", typeof(int), typeof(ui3X));
public int uiBinding1
{
get { return (int)GetValue(uiBinding1Property); }
set { SetValue(uiBinding1Property, value); }
}
public static readonly DependencyProperty uiBinding2Property = DependencyProperty.Register("uiBinding2", typeof(int), typeof(ui3X));
public int uiBinding2
{
get { return (int)GetValue(uiBinding2Property); }
set { SetValue(uiBinding2Property, value); }
}
public static readonly DependencyProperty uiBinding3Property = DependencyProperty.Register("uiBinding3", typeof(int), typeof(ui3X));
public int uiBinding3
{
get { return (int)GetValue(uiBinding3Property); }
set { SetValue(uiBinding3Property, value); }
}
}
}
here is user control xaml
<UserControl x:Class="btsRV7config.controls.ui3X"
x:Name="root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal" Height="22">
<RadioButton Name="rdbttn1" VerticalAlignment="Center" Margin="0 0 10 0"
IsChecked="{Binding ElementName=root, Path=uiBinding1}"
Click="_event" />
<RadioButton Name="rdbttn2" VerticalAlignment="Center" Margin="0 0 10 0"
IsChecked="{Binding ElementName=root, Path=uiBinding2}"
Click="_event" />
<RadioButton Name="rdbttn3" VerticalAlignment="Center"
IsChecked="{Binding ElementName=root, Path=uiBinding3}"
Click="_event" />
<TextBox Name="txtb" Margin="5 0 0 0" Width="20" Height="17" /> <!-- tempRemove -->
</StackPanel>
</UserControl>
here is an example of the user control used in MainWindow.xaml
<Window x:Class="btsRV7config.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:btsRV7config.controls"
xmlns:converter="clr-namespace:btsRV7config.converters"
Title="Vans RV7 Configuration" Height="350" Width="525" >
<Window.Resources>
<converter:Int_Int_Bool_Converter x:Key="Int_Int_Bool" />
</Window.Resources>
<Grid>
<controls:ui3X uiName="Font Color" ui1="Black" ui2="Green" ui3="Cyan"
uiBinding1="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=0}"
uiBinding2="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=1}"
uiBinding3="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=2}" />
</Grid>
</Window>
here is MainWindow.xaml.cs
namespace btsRV7config
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
record data = new record();
DataContext = data;
}
}
public class record : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _RV7sld_DFfontColor = RV7sld_dict["DFfontColor"];
public int RV7sld_DFfontColor
{
get
{ return _RV7sld_DFfontColor; }
set
{
_RV7sld_DFfontColor = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("RV7sld_DFfontColor"));
}
}
}
}
}
Sorry for posting so much code - I think the important is the user controls xaml.cs at top.
here is a link to a picture of the UI.
I've simplified the code I've posted to fit.
http://www.baytower.ca/photo/uiSample.jpg
So - 'Font Color'(RV7sld_DFfontColor) can be black(0) green(1) cyan(2)
Danny
The BindingOperations class enables you to "force" the binding updates in either direction.
Let's say there is a string property X in the code behind and there is a TextBox in XAML, bound to that property:
// C#:
public string X { get; set; }
// XAML:
<TextBox Name="textBox1" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:MainWindow, AncestorLevel=1}, Path=X}" />
To copy from textBox1.Text to X do the following:
BindingOperations.GetBindingExpression(textBox1, TextBox.TextProperty).UpdateSource();
To copy from X to textBox1.Text do the following:
BindingOperations.GetBindingExpression(textBox1, TextBox.TextProperty).UpdateTarget();
This question already has answers here:
How to use DataTemplateSelector with ContentControl to display different controls based on the view-model?
(2 answers)
Closed 4 years ago.
I'm trying to write a simple dialog that would accept a value in a SpinEdit or a text in a TextEdit. I'm using multiple VMs and I made a selector that should view a proper control based on the logic in the c++/cli file.
XAML:
xmlns:local="clr-namespace:asd"
Title="{Binding Path=Title, Mode=OneTime}"
<dx:DXWindow.Resources>
<DataTemplate x:Key="TInputValueVM" DataType="{x:Type local:TInputValueVM}">
<dxe:SpinEdit Height="23" Width="200"
Text="{Binding Value, Mode=TwoWay}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}" />
</DataTemplate>
<DataTemplate x:Key="TInputTextVM" DataType="{x:Type local:TInputTextVM}">
<dxe:TextEdit Height="23" Width="200"
Text="{Binding Value, Mode=TwoWay}"
MaskType="RegEx" Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"/>
</DataTemplate>
<local:PropertyDataTemplateSelector x:Key="templateSelector"
DataTemplate_Value="{StaticResource TInputValueVM}"
DataTemplate_Text="{StaticResource TInputTextVM}" />
</dx:DXWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" >
<Label x:Uid="Label" MinHeight="24" MinWidth="60" Content="Value" />
<ContentControl Content="{Binding Path=Whoami}" ContentTemplateSelector="{StaticResource templateSelector}" />
</StackPanel>
<StackPanel Grid.Row="1" x:Uid="OKCancel_Buttons" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Height="23" x:Name="OK_Button" Click="OK_Click" Content="OK" IsDefault="True" HorizontalAlignment="Right" MinWidth="95" />
<Button Height="23" x:Name="Cancel_Button" Click="Cancel_Click" Content="Cancel" HorizontalAlignment="Right" MinWidth="95" />
</StackPanel>
</Grid>
In c# I have a base VM and two VMS that extend it, one for values and one for text. The rest of the properties stay the same.
C#
namespace asd
{
public class TInputBaseVM : ViewModelBase
{
private string m_sTitle;
private string m_sMask;
private int m_nInputLenght;
private string m_sWhoami;
public TInputBaseVM(string A_sTitle, string A_sMask, int A_nInputLength)
{
m_sTitle = A_sTitle;
m_sMask = A_sMask;
m_nInputLenght = A_nInputLength;
}
protected string Title
{
get { return m_sTitle; }
set { SetProperty(ref m_sTitle, value, () => Title); }
}
protected string Mask
{
get { return m_sMask; }
set { SetProperty(ref m_sMask, value, () => Mask); }
}
protected int InputLength
{
get { return m_nInputLenght; }
set { SetProperty(ref m_nInputLenght, value, () => InputLength); }
}
protected string Whoami
{
get { return m_sWhoami; }
set { SetProperty(ref m_sWhoami, value, () => Whoami); }
}
}
public class TInputValueVM : TInputBaseVM
{
public TInputValueVM(string A_sTitle, string A_sMask, int A_nInputLength, double A_nValue) : base(A_sTitle, A_sMask, A_nInputLength)
{
Value = A_nValue;
Whoami = "Value";
}
private double m_nValue;
public double Value
{
get { return m_nValue; }
set { SetProperty(ref m_nValue, value, () => Value); }
}
}
public class TInputTextVM : TInputBaseVM
{
public TInputTextVM(string A_sTitle, string A_sMask, int A_nInputLength, string A_sValue) : base(A_sTitle, A_sMask, A_nInputLength)
{
Value = A_sValue;
Whoami = "Text";
}
private string m_sValue;
public string Value
{
get { return m_sValue; }
set { SetProperty(ref m_sValue, value, () => Value); }
}
}
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplate_Value { get; set; }
public DataTemplate DataTemplate_Text { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selector = item as string;
if(selector == "Value")
return DataTemplate_Value;
return DataTemplate_Text;
}
}
}
In c++/cli I create an object of a proper VM and I'd like the WPF to automatically update the view to either spinedit or textedit, however I'm not sure how to properly bind the properties from the C#. If I explicitly type 'Value' in the Content property of the ContentControl then it displays the spinEdit but I don't know how to bind it so it automatically takes the correct property.
EDIT: I'm adding c++/cli code to show how I choose different VMs
C++/cli:
bool TSignalNumberPositionDialogCLR::StartDialog(TSignalNumberPositionSupport& A_Attributes, HWND A_hwndParent, LPTSTR String)
{
try
{
TInputValueVM ^oExchange_Value;
TInputTextVM ^oExchange_Text;
int inputFormat = A_Attributes.GetInputFormat();
if(inputFormat)
oExchange_Text = gcnew TInputTextVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), gcnew System::String(A_Attributes.GetInitialText()));
else
oExchange_Value = gcnew TInputValueVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), A_Attributes.GetInitialValue());
Dialogs::TSignalNumberPositionDialog^ dialog = gcnew Dialogs::TSignalNumberPositionDialog();
if(inputFormat)
dialog->DataContext = oExchange_Text;
else
dialog->DataContext = oExchange_Value;
dialog->ShowDialog();
if(dialog->DialogResult)
{
CString nValue;
if(inputFormat)
nValue = oExchange_Text->Value;
else
nValue = ((Decimal)oExchange_Value->Value).ToString("F2", CultureInfo::InvariantCulture);
A_Attributes.UpdateValue(nValue, String, A_Attributes.GetInputLength());
return true;
}
return false;
}
catch(Exception^ e)
{
e;
}
}
based on the 'inputFormat' variable I want to display different controls in the dialog.
EDIT: Based on #Clemens comments I got rid of the selector sectionand the x:Key property in the DataTemplates. I changed the content opf the Content property to Content="{Binding}" and it somehow works. The moment I create a VM it selects the correct one.
Based on your comment. Let me improve my answer. As you are facing issue in VM selection. so plesae concentrate how I assigned VM to datatemplate. Although it is done in very basic way, you can handle it if you you are using MVVM packages.
I have created 2 data template and 2 vms and each vm is bound to datatemplate. To verify, I have a combobox, which will select datatemplate based on selected value.
Here is Sample VM
public class VM : System.ComponentModel.INotifyPropertyChanged
{
private string title;
private SolidColorBrush background;
public string Title { get => title; set { title = value; RaisePropertyChanged(); } }
public SolidColorBrush Background { get => background; set { background = value; RaisePropertyChanged(); } }
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class VM1: VM
{
public VM1()
{
Title = "This is VM1";
Background = Brushes.Yellow;
}
}
public class VM2: VM
{
public VM2()
{
Title = "This is VM2";
Background = Brushes.Orange;
}
}
Now check for resources
<local:VM1 x:Key="VM1"/>
<local:VM2 x:Key="VM2"/>
<DataTemplate x:Key="DT1">
<Grid DataContext="{StaticResource VM1}">
<TextBlock Text="{Binding Title}" Background="{Binding Background}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="DT2">
<Grid DataContext="{StaticResource VM2}">
<TextBlock Text="{Binding Title}" Background="{Binding Background}"/>
</Grid>
</DataTemplate>
<Style TargetType="ContentControl" x:Key="contentStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cmbo, Path=SelectedValue}" Value="Template1">
<Setter Property="ContentTemplate" Value="{StaticResource DT1}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=cmbo, Path=SelectedValue}" Value="Template2">
<Setter Property="ContentTemplate" Value="{StaticResource DT2}" />
</DataTrigger>
</Style.Triggers>
</Style>
and finally I have combobox and content control just to verify
<ComboBox Name="cmbo"/>
<ContentControl Style="{StaticResource contentStyle}"/>
where cmbo.ItemsSource = new List { "Template1", "Template2" };
Hope you got the point
I'm facing a problem in my WPF project at the moment. At this moment I have a Viewmodel which has a Manager (to communicate with the repo).
internal class TicketViewModel
{
private TicketManager mgr;
public IEnumerable<Ticket> List { get; set; }
public TicketViewModel()
{
mgr = new TicketManager();
List = mgr.GetTickets();
}
}
I've managed to bind this list to the Listbox in my MainWindow. The next step is that I need to add an extra ticket to the list and also pass this through the manager. The problem is I need two parameters from some Controls in the MainWindow. From MVVM perspective I need to use bound Commands on e.g. a Button to communicate with the viewmodel as my viewmodel can't/may not access controls from the window. Is using parameterized Commands the way to go here?
The next problem is that the Listbox won't update I guess. This is the code:
<ListBox x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I found that using a CompareableCollection is the way to go here, but then I still have to read all the Tickets again after adding a new Ticket.
Thanks in advance,
Hicy
okay here is the code.
Lets say you have three textboxes on MainWindow(since you have three Textblocks.) so Your MainWindow.xaml looks like
<Window.DataContext>
<local:MyViewModel/>--set's your viewModel
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="250*"/>
<RowDefinition Height="90"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
<StackPanel Width="250">
<TextBlock Text="{Binding TicketNumber}" />
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding State}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="TicketNumber" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=Text}" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="Text" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=State}" />
<TextBox x:Name="State" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=TicketNumber}" />
<Button Content="Button" Command="{Binding Path=MainCommand}" Grid.Row="2"/>
</Grid>
and I am assuming that you have some class called class Ticket which contain these three members
Class Ticket
{
public int TicketNumber { get; set; }
public string Text { get; set; }
public string State { get; set; }
}
Now in class TicketManager we fill it with some dummy data
class TicketManager
{
ObservableCollection<Ticket> tl = new ObservableCollection<Ticket>();
internal ObservableCollection<Ticket> GetTickets()
{
tl.Add(new Ticket() { State = "State1", Text = "Text1", TicketNumber = 1 });
tl.Add(new Ticket() { State = "State2", Text = "Text2", TicketNumber = 2 });
tl.Add(new Ticket() { State = "State3", Text = "Text3", TicketNumber = 3 });
return tl;
}
}
and in your Mainwindow ViewModel lets call it MyViewModel.cs we add
class MyViewModel:INotifyPropertyChanged
{
private TicketManager mgr;
public ObservableCollection<Ticket> List { get; set; }
private string text;
private string state;
private int ticketNumber;
private readonly DelegateCommand<object> MyButtonCommand;
public Class1()
{
mgr = new TicketManager();
List = mgr.GetTickets();
MyButtonCommand = new DelegateCommand<object>((s) => { AddListToGrid(text, state, ticketNumber); }, (s) => { return !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(state); });
}
private void AddListToGrid(string text, string state, int ticketNumber)
{
List.Add(new Ticket() {Text=text,State=state,TicketNumber=ticketNumber });
}
public DelegateCommand<object> MainCommand
{
get
{
return MyButtonCommand;
}
}
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public string State
{
get
{
return state;
}
set
{
state = value;
OnPropertyChanged("State");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
public int TicketNumber
{
get
{
return ticketNumber;
}
set
{
ticketNumber = value;
OnPropertyChanged("TicketNumber");
MyButtonCommand.RaiseCanExecuteChanged();
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can Modify the code in anyway you want
This ViewModel implements fewthings which are very important from MVVM point of view
1) INotifyPropertyChanged
2) WPF Delegate Command
P.S:The code is tested and it runs as expected
Don't get hung up on MVVM it is simply a separation of data from a view, and models are shared between the two with a majority of the business logic (on a shared component) should be performed on the VM; it is not a religion just a three tiered data system. IMHO
If your button needs to do an operation, have it make a call, most likely in the code behind, to a method on the VM which handles the business logic, updates the list with the new item and notifies the manager.
I would bind the list in question to an ObservableCollection which can notify upon insert/delete of an item.
im building a UserControl MyUserControl that has his own ViewModel MyUserControlViewModel. MyUserControl contains 6 VehicleSelectionBlock (V1, ... V6). VehicleSelectionBlock is a UserControl i've made. it has 3 RadioButton: car, train, bus; all are of enum type Vehicle and of the same GroupName VehicleGroup.
my goal is to represent each of MyUserControl's VehicleSelectionBlocks in MyUserControlViewModel.
to make my self clear: in MyUserControlViewModel i want to be able to know&change what RadioButton is checked in every one of the 6 VehicleSelectionBlock. i think my main problem is not the converter but rather the DataContex - i'm not sure how to set it correctly for each of the controllers.
iv'e tried Binding (which is the obvious solution). i tried reading here, here , and here. unfortunately neither one helped my acheive my goal.
my code is below - im kinda new to wpf and data binding in generally. i've read almost every chapter in this tutorial but still lost sometimes.
please help me get through this and understand better the DataContex concept.
ty
MyUserContlor.xaml.cs:
namespace Project01
{
/// <summary>
/// Interaction logic for MyUserContlor.xaml
/// </summary>
public partial class MyUserContlor : UserControl
{
public MyUserContlorViewModel ViewModel { get; set; }
public MyUserContlor()
{
ViewModel = new MyUserContlorViewModel();
InitializeComponent();
this.DataContext = ViewModel;
}
private void BtnImReady_OnClick(object sender, RoutedEventArgs e)
{
//this code is irrelevant to the question
throw NotImplementedException();
}
}
}
MyUserContlor.xaml:
<UserControl x:Class="Project01.MyUserContlor"
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:loc="clr-namespace:Project01"
mc:Ignorable="d"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
<Viewbox Stretch="Uniform">
<StackPanel>
<loc:VehicleSelectionBlock Name="V1"/>
<loc:VehicleSelectionBlock Name="V2"/>
<loc:VehicleSelectionBlock Name="V3"/>
<loc:VehicleSelectionBlock Name="V4"/>
<loc:VehicleSelectionBlock Name="V5"/>
<loc:VehicleSelectionBlock Name="V6"/>
<Button x:Name="BtnImReady" Click="BtnImReady_OnClick">Im Ready!</Button>
</StackPanel>
</Viewbox>
</UserControl>
MyUserContlorViewModel.cs:
namespace Project01
{
public class MyUserContlorViewModel : INotifyPropertyChanged
{
public MyUserContlorViewModel()
{
VehicleArr = new MyViewModel_Vehicle[6];
PropertyChanged+=MyUserControlViewModel_PropertyChanged;
}
public MyViewModel_Vehicle[] VehicleArr;
public event PropertyChangedEventHandler PropertyChanged;
public PropertyChangedEventHandler GetPropertyChangedEventHandler() { return PropertyChanged; }
private void MyUserControlViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//might be useful
throw NotImplementedException();
}
}
//this class should represent a VehicleSelectionBlock
public class MyViewModel_Vehicle
{
public Vehicle VehicleSelected {get; set;}
MyViewModel_Vehicle(){}
MyViewModel_Vehicle(Vehicle v){ VehicleSelected = v;}
}
}
VehicleSelectionBlock.xaml:
<UserControl x:Class="Project01.VehicleSelectionBlock"
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:Project01"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Border VerticalAlignment="Center" HorizontalAlignment="Center" Background="GhostWhite"
BorderBrush="Gainsboro" BorderThickness="1">
<StackPanel >
<Label Content="{Binding Name}"
FontWeight="Bold" HorizontalContentAlignment="Center"></Label>
<RadioButton GroupName="VehicleGroup" >car</RadioButton>
<RadioButton GroupName="VehicleGroup">train</RadioButton>
<RadioButton GroupName="VehicleGroup" IsChecked="True">bus</RadioButton>
</StackPanel>
</Border>
</Grid>
</UserControl>
VehicleSelectionBlock.xaml.cs:
namespace Project01
{
/// <summary>
/// Interaction logic for VehicleSelectionBlock.xaml
/// </summary>
public partial class VehicleSelectionBlock : UserControl
{
public VehicleSelectionBlock()
{
InitializeComponent();
}
public VehicleSelectionBlock(String name)
{
name = Name;
InitializeComponent();
}
public static readonly DependencyProperty NameProperty = DependencyProperty.Register(
"Name", typeof (String), typeof (VehicleSelectionBlock), new PropertyMetadata(default(String)));
public String Name
{
get { return (String) GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
public enum Vehicle { Car, Train, Bus}
}
here is a quick solution. keep in mind that the code needs to change if you want to add more values to your Vehicle enum.
the MyUserControlViewModel.cs file
public class MyUserControlViewModel
{
public MyUserControlViewModel()
{
VehicleArr = new VehicleViewModel[6];
for (int i = 0; i < 6;i++ )
VehicleArr[i] = new VehicleViewModel();
}
public VehicleViewModel[] VehicleArr { get; set; }
}
this will expose your 6 items. They could be more. As a result they will be displayed in an ItemsControl, as you will see later.
public class VehicleViewModel:ViewModelBase
{
private bool isCar, isTrain, isBus;
public bool IsCar
{
get { return isCar; }
set
{
if (isCar == value) return;
isCar = value;
OnChanged("IsCar");
}
}
public bool IsTrain
{
get { return isTrain; }
set
{
if (isTrain == value) return;
isTrain = value;
OnChanged("IsTrain");
}
}
public bool IsBus
{
get { return isBus; }
set
{
if (isBus == value) return;
isBus = value;
OnChanged("IsBus");
}
}
}
instances of VehicleViewModel will contain your radio selection using 3 bool properties. this is the solution disadvantage. If you want more values you'll have to add more properties. you can see this inherits ViewModelBase. ViewModelBase just implements INPC so i'm not going to put it here. ViewModelBase also exposes the OnChange method that triggers the INPC event.
displaying the list can be done in your MyUserControl by using an ItemsControl like below.
<ItemsControl ItemsSource="{Binding VehicleArr}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<loc:VehicleControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
each item is also a UserControl. The VehicleControl user control is just a StackPanel that displays the RadioButons. This can be seen below.
<StackPanel Orientation="Horizontal">
<RadioButton Content="Car" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsCar, Mode=TwoWay}"/>
<RadioButton Content="Train" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsTrain, Mode=TwoWay}"/>
<RadioButton Content="Bus" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsBus, Mode=TwoWay}"/>
</StackPanel>
please notice that each RadioButton is bound to one of the 3 properties in the VehicleViewModel instance.
Once you press your button you should have all the selections recorded. if you want you could have a function that returns an enum value by analysing the 3 bool properties if that is what you need.
the best solution will be to get rid of the radio buttons and replace them with combo boxes. in this way you can change the enum members and everything will continue to work without changing anything else. this might look as below.
public class VehicleViewModel:ViewModelBase
{
private Vehicle selOption;
private readonly Vehicle[] options;
public VehicleViewModel()
{
this.options = (Vehicle[])Enum.GetValues(typeof(Vehicle));
}
public Vehicle[] Options { get { return options; } }
public Vehicle SelectedOption
{
get { return selOption; }
set
{
if (selOption == value) return;
selOption = value;
OnChanged("SelectedOption");
}
}
}
and for the view:
<ItemsControl ItemsSource="{Binding VehicleArr}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Options}"
SelectedItem="{Binding SelectedOption, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can do directly in the code-behind of your control (in the default constructor)
public VehicleSelectionBlock()
{
InitializeComponent();
this.DataContext = new MyUserContlorViewModel ();
}
You can also do that in XAML (http://msdn.microsoft.com/en-us/library/ms746695(v=vs.110).aspx) declaration, as you wish.
I am working on a silverlight 5 existing application where MVVM approached is followed.
I have created a my own ErrorMessageBox.xaml (childwindow) in View folder and i am in situation where this ErrorMessageBox must be popuped in a class inside Model folder.
And i found that the ErrorMessageBox are not accessible in Model (because it is in View folder).So at last i created one more ErrorMessageBox.xaml inside Model so that it will be used in all
classes in Model folder.
And when i try to popup this child window(ErrorMessageBox.xaml) then it do not pop up. Why it happens and how to Pop up this ErrorMessageBox.xaml inside a function call in a class in Model
folder.
public static void ThisFunctionIsCalledIHaveVerifiedOnDebugging(string message) //it is inside a class in Model folder
{
ConfirmationWindow cfw = new ConfirmationWindow();
cfw.SetMessage("Popup test");
cfw.Show(); //it do not pop up it
}
And ConfirmationWindow.xaml is :
<silvercontrols:ChildWindow x:Class="Model.MessageFolder.ConfirmationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:silvercontrols="clr-namespace:Silverlight.Windows.Controls;assembly=Silverlight.Windows.Controls"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
Title="Message" Width="Auto" Height="Auto" MouseRightButtonDown="ChildWindow_MouseRightButtonDown">
<silvercontrols:ChildWindow.Style>
<StaticResource ResourceKey="MessageBoxStyle"/>
</silvercontrols:ChildWindow.Style>
<Grid x:Name="LayoutRoot" MinWidth="360">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="MessageBox" Margin="10 15 0 0" Height="Auto" FontSize="12" Text="Text" Foreground="White" TextWrapping="Wrap" HorizontalAlignment="Left" />
<StackPanel x:Name="ContentBox" Margin="10 15 0 0" Height="Auto" Orientation="Horizontal"></StackPanel>
<StackPanel Margin="0 0 0 10" Orientation="Horizontal" HorizontalAlignment="Center" Height="45">
<Button x:Name="YesBtn" Content="Yes" Width="82" HorizontalAlignment="Left" VerticalAlignment="Bottom" Style="{StaticResource ButtonStyle_Blue}"/>
<Button x:Name="NoBtn" Content="No" Margin="60 0 0 0" Width="82" HorizontalAlignment="Right" VerticalAlignment="Bottom" Style="{StaticResource ButtonStyle_Blue}"/>
</StackPanel>
</StackPanel>
</Grid>
</silvercontrols:ChildWindow>
and ConfirmationWindow.xaml.cs is :
using System.Windows;
namespace Model.MessageFolder
{
public partial class ConfirmationWindow : Silverlight.Windows.Controls.ChildWindow
{
private bool showBtnClose;
public ConfirmationWindow(bool showBtnClose = false)
{
InitializeComponent();
HasCloseButton = showBtnClose;
this.showBtnClose = showBtnClose;
NoBtn.Click += Close;
}
#region METHODS
public void SetMessage(string message)
{
MessageBox.Text = message;
}
public void AddContent(UIElement elt)
{
ContentBox.Children.Add(elt);
}
#endregion
#region EVENT_HANDLER
public void Close(object sender, RoutedEventArgs e)
{
this.Close();
}
#endregion
private void ChildWindow_MouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
}
}
}
Why it is not working? How to make it work ?
First thing is that you should not bring your childwindow class in the models folder because it breaks the MVVM pattern. Instead leave it in your views folder.
What you should do is to show the childwindow from your model's view.
To achieve that you need a way to tell your view when to show the childwindow and what message it should display.
First, in your model create a property ErrorMessage:
public class MyModel : INotifyPropertyChanged
{
...
private string _errorMessage;
public string ErrorMessage
{
private set
{
_errorMessage = value;
OnPropertyChanged("ErrorMessage");
}
get { return _errorMessage;; }
}
...
}
Note: I assume here that your model class implements INotifyPropertyChanged interface but it could be a different implementation.
Then in your view's code behind add a dependency property and bind it to your model's ErrorMessage.
The dependency property's change callback is used to display the childwindow.
This could look like the following:
public partial class MyView : UserControl
{
...
public static readonly DependencyProperty ErrorMessageProperty =
DependencyProperty.Register("ErrorMessage", typeof (string), typeof (MyView),
new PropertyMetadata((o, args) =>
{
// Display childwindow when message is changed
string message = args.NewValue as string;
if(message!=null)
{
ConfirmationWindow cfw = new ConfirmationWindow();
cfw.SetMessage(message);
cfw.Show();
}
}));
public string ErrorMessage
{
get { return (string)GetValue(ErrorMessageProperty); }
private set { SetValue(ErrorMessageProperty, value); }
}
...
public MyModel ViewModel
{
...
set
{
DataContext = value;
Binding binding = new Binding();
binding.Source = value;
binding.Path = new PropertyPath("ErrorMessage");
SetBinding(ErrorMessageProperty, binding);
}
...
}
...
}
Then every time you change the value of ErrorMessage in your model it should show the childwindow.