How to bind to TextBox.Text in local ValidationRule class - c#

This is my XAML file:
<Window x:Class="WpfListView.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:WpfListView"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView Margin="10" Name="lvUsers">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Margin="0,0,5,0">
<Binding Path="Mail" Mode="TwoWay">
<Binding.ValidationRules>
<local:NameValidation>
<local:NameValidation.Params>
<local:NameValidationParameters
OriginalTree="{Binding Source={x:Reference lvUsers}}"
OriginalName="{Binding RelativeSource={RelativeSource Self}}"/> <!-- I want the OriginalName to be TextBox.Text-->
</local:NameValidation.Params>
</local:NameValidation>
</Binding.ValidationRules>
</Binding>
</TextBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
This is my MainWindow class:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42, Mail = "john#doe-family.com" });
items.Add(new User() { Name = "Jane Doe", Age = 39, Mail = "jane#doe-family.com" });
items.Add(new User() { Name = "Sammy Doe", Age = 7, Mail = "sammy.doe#gmail.com" });
lvUsers.ItemsSource = items;
}
}
And this is my ValidationRule class:
public class NameValidationParameters : DependencyObject
{
public ListView OriginalTree
{
get { return (ListView)this.GetValue(OriginalTreeProperty); }
set { this.SetValue(OriginalTreeProperty, value); }
}
public string OriginalName
{
get { return (string)this.GetValue(OriginalNameProperty); }
set { this.SetValue(OriginalNameProperty, value); }
}
public static readonly DependencyProperty OriginalTreeProperty
= DependencyProperty.Register(nameof(OriginalTree), typeof(ListView),
typeof(NameValidationParameters));
public static readonly DependencyProperty OriginalNameProperty
= DependencyProperty.Register(nameof(OriginalName), typeof(string),
typeof(NameValidationParameters));
}
public class NameValidation : ValidationRule
{
public string ErrorMessage
{ get; set; }
public NameValidationParameters Params { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
ValidationResult lResult = null;
return lResult;
}
}
As you can see, when the NameValidation.Validate is called, I want NameValidation.Params to be populated with correct variables:
NameValidation.Params.OriginalTree should be the original ListView. This I can get.
NameValidation.Params.OriginalName should be the current TextBox's Text. In this case, it is bound to Mail, so I would expect to see that it is the email address like john#doe-family.com. However I can't get this out; all I can see is that NameValidation.Params.OriginalName is "WpfListView.NameValidationParameters"
Also, I want to be able to access the current index in the list inside the NameValidation.Validate method. How can I get it?

As mentioned elsewhere,
Validation rules do not hold or inherit a DataContext this will prevent binding from working as expected if you try to declare everything inline. Instead, declare your bindable rule options as a resource and set the property on your custom rule using a StaticBinding.
I've modified your TextBox to add a resource parameter:
<TextBox Grid.Column="0" Margin="0,0,5,0" x:Name="box1">
<TextBox.Resources>
<local:NameValidationParameters OriginalName="{Binding Mail}" OriginalTree="{Binding Source={x:Reference lvUsers}}" x:Key="Parameters"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="Mail" Mode="TwoWay">
<Binding.ValidationRules>
<local:NameValidation Params="{StaticResource Parameters}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
And NameValidationParameters to implement Freezable
public class NameValidationParameters : Freezable
{
public ListView OriginalTree
{
get { return (ListView)this.GetValue(OriginalTreeProperty); }
set { this.SetValue(OriginalTreeProperty, value); }
}
public string OriginalName
{
get { return (string)this.GetValue(OriginalNameProperty); }
set { this.SetValue(OriginalNameProperty, value); }
}
public static readonly DependencyProperty OriginalTreeProperty
= DependencyProperty.Register(nameof(OriginalTree), typeof(ListView),
typeof(NameValidationParameters));
public static readonly DependencyProperty OriginalNameProperty
= DependencyProperty.Register(nameof(OriginalName), typeof(object),
typeof(NameValidationParameters));
protected override Freezable CreateInstanceCore()
{
return new NameValidationParameters();
}
}
However, Here is an example of how i implemented INotifyDataErrorInfo in my project. This is a very clean way to trigger validation errors and xaml allows you to configure the display of errors
Model class:
public class AttributionInput
{
[Required]
public DateTime? StartDate { get; set; }
[Required]
public DateTime? EndDate { get; set; }
}
ModelWrapper Generic:
public class ModelWrapper<T> : NotifyDataErrorInfoBase
{
public T Model { get; protected set; }
public ModelWrapper(T model)
{
Model = model;
}
public ModelWrapper()
{
}
protected virtual TValue GetValue<TValue>([CallerMemberName] string propertyName = null)
{
return (TValue)typeof(T).GetProperty(propertyName)?.GetValue(Model);
}
protected virtual void SetValue<TValue>(TValue value, [CallerMemberName] string propertyName = null)
{
typeof(T).GetProperty(propertyName)?.SetValue(Model, value);
OnPropertyChanged(propertyName);
ValidatePropertyInternal(propertyName, value);
}
private void ValidatePropertyInternal(string propertyName, object currentValue)
{
ClearErrors(propertyName);
ValidateDataAnnotations(propertyName, currentValue);
ValidateCustomErrors(propertyName);
}
protected virtual IEnumerable<string> ValidateProperty(string propertyName)
{
return null;
}
private void ValidateCustomErrors(string propertyName)
{
var errors = ValidateProperty(propertyName);
if (errors != null)
{
foreach (var error in errors)
{
AddError(propertyName, error);
}
}
}
private void ValidateDataAnnotations(string propertyName, object currentValue)
{
var results = new List<ValidationResult>();
var context = new ValidationContext(Model) { MemberName = propertyName };
Validator.TryValidateProperty(currentValue, context, results);
foreach (var result in results)
{
AddError(propertyName, result.ErrorMessage);
}
}
}
Generic Implementation:
public class AttributionInputWrapper : ModelWrapper<AttributionInput>
{
public AttributionInputWrapper(AttributionInput model) : base(model)
{
}
public DateTime? StartDate
{
get => GetValue<DateTime?>();
set
{
SetValue(value);
if (EndDate < StartDate) EndDate = StartDate;
}
}
public DateTime? EndDate
{
get => GetValue<DateTime?>();
set
{
SetValue(value);
if (EndDate < StartDate) StartDate = EndDate;
}
}
protected override IEnumerable<string> ValidateProperty(string propertyName)
{
if (propertyName == nameof(EndDate) || propertyName == nameof(StartDate))
{
//if (StartDate.Value.Date > EndDate.Value.Date) yield return "Start Date must be <= End Date";
if (EndDate != null && (EndDate.Value.DayOfWeek == DayOfWeek.Saturday || EndDate.Value.DayOfWeek == DayOfWeek.Sunday))
yield return "Please select a week day";
if (StartDate != null && (StartDate.Value.DayOfWeek == DayOfWeek.Saturday || StartDate.Value.DayOfWeek == DayOfWeek.Sunday))
yield return "Please select a week day";
}
}
}
ViewModel:
public class QueryViewModel : DetailViewModelBase, ICommonViewModel
{
private AttributionInputWrapper _attributionInput;
public AttributionInputWrapper AttributionInput
{
get => _attributionInput;
set
{
_attributionInput = value;
OnPropertyChanged();
}
}
}
View :
<Style TargetType="DatePicker">
<Setter Property="Margin" Value="{StaticResource MarginString}"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="Placeholder1"/>
<TextBlock Text="{Binding ElementName=Placeholder1, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" FontSize="9"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<DatePicker x:Name="StartDateBox" SelectedDate="{Binding AttributionInput.StartDate, UpdateSourceTrigger=PropertyChanged}" DisplayDateStart="10/01/2017" DisplayDateEnd="{x:Static system:DateTime.Now}"/>

Related

How to update/convert mumeric TextBox value when changing value's unit using a ComboBox? Value normalization based on current unit?

I would like to have a converter system for my Xamarin and WPF project. I don't want to save any units in the database, so I want directly convert the textbox-values when user change the unit.
I made public a few Observable Collections like;
public class AreaList : ObservableCollection<Unit>
{
public AreaList() : base()
{
Add(new Unit("mm²"));
Add(new Unit("cm²"));
Add(new Unit("dm²"));
Add(new Unit("m²"));
}
}
public class Unit
{
private string name;
public Unit(string name)
{
this.name = name;
}
public string Name
{
get { return name; }
set { name = value; }
}
}
In the View i bind the collection to my combo box. I gave my TextBox the name of his binding property(Text="{Binding TxtBoxValue}" => x:Name="TxtBoxValue"). The ConvertUnitValueCommand set this name as a string in the view model to know which variable the converter function should use when the unit is changed.
View
<UserControl.Resources>
<c:AreaList x:Key="AreaListData" />
</UserControl.Resources>
<TextBox x:Name="TxtBoxValue"
Text="{Binding Mode=TwoWay, Path=TxtBoxValue, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<ComboBox IsSynchronizedWithCurrentItem="True"
IsEditable="False"
DisplayMemberPath="Name"
SelectedItem="{Binding Unit,Mode=OneWayToSource}"
ItemsSource="{Binding Source={StaticResource AreaListData}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding ConvertUnitValueCommand}"
CommandParameter="{Binding ElementName=TxtBoxValue, Path=Name}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
ViewModel
private string ConvertControlName;
private void ConvertUnitValue(object obj)
{
ConvertControlName = obj.ToString();
}
public Unit Unit
{
get => Get<Unit>();
set
{
if (ConvertControlName != null)
{
FieldInfo variable = this.GetType().GetField(ConvertControlName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic);
//Get the Value from setted Binding Variable
double oldValue = (double)variable.GetValue(this);
//Convert the value
if (oldValue > 0)
{
double newValue = Converts.ConvertUnitValue(Unit, value, oldValue);
variable.SetValue(this, newValue);
}
Set(value);
}
}
Maybe anyone can give me some inspiration to do it better.
The following example normalizes the user input to the base unit m²:
Unit.cs
public class Unit
{
public Unit(string name, decimal baseFactor)
{
this.Name = name;
this.BaseFactor = baseFactor;
}
#region Overrides of Object
/// <inheritdoc />
public override string ToString() => this.Name;
#endregion
public string Name { get; set; }
public decimal BaseFactor { get; set; }
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Units = new List<Unit>()
{
new Unit("mm²", (decimal) (1 / Math.Pow(1000, 2))),
new Unit("cm²", (decimal) (1 / Math.Pow(100, 2))),
new Unit("dm²", (decimal) (1 / Math.Pow(10, 2))),
new Unit("m²", 1)
};
}
private void NormalizeValue()
{
this.NormalizedValue = this.UnitValue * this.SelectedUnit.BaseFactor;
}
private List<Unit> units;
public List<Unit> Units
{
get => this.units;
set
{
this.units = value;
OnPropertyChanged();
}
}
private Unit selectedUnit;
public Unit SelectedUnit
{
get => this.selectedUnit;
set
{
this.selectedUnit = value;
OnPropertyChanged();
NormalizeValue();
}
}
private decimal unitValue;
public decimal UnitValue
{
get => this.unitValue;
set
{
this.unitValue = value;
OnPropertyChanged();
NormalizeValue();
}
}
private decimal normalizedValue;
public decimal NormalizedValue
{
get => this.normalizedValue;
set
{
this.normalizedValue = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ManiWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DatContext>
<StackPanel>
<!-- Input -->
<TextBox Text="{Binding UnitValue}" />
<ComboBox ItemsSource="{Binding Units}"
SelectedItem="{Binding SelectedUnit}" />
<TextBlock Text="{Binding NormalizedValue}" />
</StackPanel>
</Window>
Reusable solution
A reusable solution would be to create a custom control, which derives from TextBox and encapsulates the normalization logic and the control design.
The following custom control NormalizingNumericTextBox extends TextBox and converts two way from non-normalized value to normalized and back.
It is basically a TextBox aligned with a ComboBox as Unit selector.
It may not be perfect, but it is ready to use and it just took me about 10 minutes to merge the previous answer into this custom control.
NormalizingNumericTextBox supports any type of unit describing a numeric value.
Just bind the NormalizingNumericTextBox.Units property to collection of any kind of Unit implementation e.g. weight, length, currency, etc.
Bind to NormalizingNumericTextBox.NormalizedValue to get/set the normalized value. Setting this property will convert the value to the current NormalizingNumericTextBox.SelectedUnit.
Bind to NormalizingNumericTextBox.Text for the raw input value.
Ensure that the default Style (see below) is added to the ResourceDictionary inside /Themes/Generic.xaml. Customize this Style to customize appearance.
ManiWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DatContext>
<StackPanel>
<!-- Input -->
<NormalizingUnitTextBox NormalizedValue="{Binding NormalizedValue}"
Units="{Binding Units}"
Width="180" />
<!--
Test to show/manipulate current normalized value of the view model.
An entered normalized value will be converted back to the current NormalizingNumericTextBox.Unit -->
<TextBox Background="Red" Text="{Binding NormalizedUnitValue}"/>
</StackPanel>
</Window>
Unit.cs
public class Unit
{
public Unit(string name, decimal baseFactor)
{
this.Name = name;
this.BaseFactor = baseFactor;
}
#region Overrides of Object
/// <inheritdoc />
public override string ToString() => this.Name;
#endregion
public string Name { get; set; }
public decimal BaseFactor { get; set; }
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Units = new List<Unit>()
{
new Unit("m²", 1),
new Unit("dm²", (decimal) (1/Math.Pow(10, 2))),
new Unit("cm²", (decimal) (1/Math.Pow(100, 2))),
new Unit("mm²", (decimal) (1/Math.Pow(1000, 2)))
};
}
public List<Unit> Units { get; set; }
private decimal normalizedValue;
public decimal NormalizedValue
{
get => this.normalizedValue;
set
{
this.normalizedValue = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
NormalizingNumericTextBox.cs
[TemplatePart(Name = "PART_UnitsItemsHost", Type = typeof(ItemsControl))]
public class NormalizingNumericTextBox : TextBox
{
public static readonly DependencyProperty UnitsProperty = DependencyProperty.Register(
"Units",
typeof(IEnumerable<Unit>),
typeof(NormalizingNumericTextBox),
new PropertyMetadata(default(IEnumerable<Unit>), NormalizingNumericTextBox.OnUnitsChanged));
public IEnumerable<Unit> Units
{
get => (IEnumerable<Unit>) GetValue(NormalizingNumericTextBox.UnitsProperty);
set => SetValue(NormalizingNumericTextBox.UnitsProperty, value);
}
public static readonly DependencyProperty SelectedUnitProperty = DependencyProperty.Register(
"SelectedUnit",
typeof(Unit),
typeof(NormalizingNumericTextBox),
new FrameworkPropertyMetadata(
default(Unit),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
NormalizingNumericTextBox.OnSelectedUnitChanged));
public Unit SelectedUnit
{
get => (Unit) GetValue(NormalizingNumericTextBox.SelectedUnitProperty);
set => SetValue(NormalizingNumericTextBox.SelectedUnitProperty, value);
}
public static readonly DependencyProperty NormalizedValueProperty = DependencyProperty.Register(
"NormalizedValue",
typeof(decimal),
typeof(NormalizingNumericTextBox),
new FrameworkPropertyMetadata(
default(decimal),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
NormalizingNumericTextBox.OnNormalizedValueChanged));
public decimal NormalizedValue
{
get => (decimal) GetValue(NormalizingNumericTextBox.NormalizedValueProperty);
set => SetValue(NormalizingNumericTextBox.NormalizedValueProperty, value);
}
private ItemsControl PART_UnitsItemsHost { get; set; }
private bool IsNormalizing { get; set; }
static NormalizingNumericTextBox()
{
FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(
typeof(NormalizingNumericTextBox),
new FrameworkPropertyMetadata(typeof(NormalizingNumericTextBox)));
}
public NormalizingNumericTextBox()
{
}
private static void OnNormalizedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var _this = d as NormalizingNumericTextBox;
_this.ConvertNormalizedValueToNumericText();
}
private static void OnSelectedUnitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as NormalizingNumericTextBox).NormalizeNumericText();
}
private static void OnUnitsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var _this = d as NormalizingNumericTextBox;
_this.SelectedUnit = _this.Units.FirstOrDefault();
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.PART_UnitsItemsHost = GetTemplateChild("PART_UnitsItemsHost") as ItemsControl;
if (this.PART_UnitsItemsHost == null)
{
throw new InvalidOperationException($"{nameof(this.PART_UnitsItemsHost)} not found in ControlTemplate");
}
this.PART_UnitsItemsHost.SetBinding(
Selector.SelectedItemProperty,
new Binding(nameof(this.SelectedUnit)) {Source = this});
this.PART_UnitsItemsHost.SetBinding(
ItemsControl.ItemsSourceProperty,
new Binding(nameof(this.Units)) {Source = this});
this.SelectedUnit = this.Units.FirstOrDefault();
}
#region Overrides of TextBoxBase
/// <inheritdoc />
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (this.IsNormalizing)
{
return;
}
NormalizeNumericText();
}
/// <inheritdoc />
protected override void OnTextInput(TextCompositionEventArgs e)
{
// Suppress non numeric characters
if (!decimal.TryParse(e.Text, NumberStyles.Number, CultureInfo.CurrentCulture, out decimal _))
{
e.Handled = true;
return;
}
base.OnTextInput(e);
}
#endregion Overrides of TextBoxBase
private void NormalizeNumericText()
{
this.IsNormalizing = true;
if (decimal.TryParse(this.Text, NumberStyles.Number, CultureInfo.CurrentCulture, out decimal numericValue))
{
this.NormalizedValue = numericValue * this.SelectedUnit.BaseFactor;
}
this.IsNormalizing = false;
}
private void ConvertNormalizedValueToNumericText()
{
this.IsNormalizing = true;
decimal value = this.NormalizedValue / this.SelectedUnit.BaseFactor;
this.Text = value.ToString(CultureInfo.CurrentCulture);
this.IsNormalizing = false;
}
}
Generic.xaml
<ResourceDictionary>
<Style TargetType="NormalizingNumericTextBox">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="DarkGray" />
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NormalizingNumericTextBox">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost" Grid.Column="0" Margin="0" />
<ComboBox x:Name="PART_UnitsItemsHost" Grid.Column="1" BorderThickness="0" HorizontalAlignment="Right" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
I have not much idea about your code impact but I would suggest you try below design which uses MVVM Pattern which removes tight coupling between UI and Backend.
I have separate out the things here
your XAML will have code like
<TextBox x:Name="unitTextbox"
Text="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
<ComboBox IsSynchronizedWithCurrentItem="True"
IsEditable="False"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedUnit}"
ItemsSource="{Binding AvailableUnits}">
</ComboBox>
Your ViewModel will be like
public class MainVm : Observable
{
#region Private Fields
private double _value;
private ObservableCollection<Unit> _availableUnits;
private Unit _selectedUnit;
private Unit _previouslySelected;
#endregion Private Fields
#region Public Constructors
public MainVm()
{
_availableUnits = new ObservableCollection<Unit>()
{
new Unit("mm²"),
new Unit("cm²"),
new Unit("dm²"),
new Unit("m²")
};
}
#endregion Public Constructors
#region Public Properties
public double Value
{
get
{
return _value;
}
set
{
if (_value != value)
{
_value = value;
OnPropertyChanged();
}
}
}
public Unit SelectedUnit
{
get { return _selectedUnit; }
set
{
_previouslySelected = _selectedUnit;
_selectedUnit = value;
// call to value conversion function
// convert cm² to mm² or anything
Value = UnitConvertor.Convert(_value, _previouslySelected.Name, _selectedUnit.Name);
OnPropertyChanged();
}
}
public ObservableCollection<Unit> AvailableUnits => _availableUnits;
#endregion Public Properties
}
My Observable class will be like
public class Observable : INotifyPropertyChanged
{
#region Public Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion Public Events
#region Protected Methods
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion Protected Methods
}
better to use an enum for units

Bind custom header controls in DataGrid

I have a custom column header where each column's header has TextBox which contains name of the column and ComboBox, which contains information about the type of the column, e.g. "Date", "Number", etc.
I'm trying to bind ComboBox and keep its value somewhere, so that when user selects new value from ComboBox I can recreate table with the column's type changed. Basically all I need is to store somehow each ComboBox's value in a list somehow. I want to do the same with TextBox which should contain name of the column.
This is what I have so far.
<DataGrid x:Name="SampleGrid" Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2" ItemsSource="{Binding SampledData}">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding ., Mode=OneWay}"/>
<ComboBox>
// How can I set it from ViewModel?
<ComboBoxItem Content="Date"></ComboBoxItem>
<ComboBoxItem Content="Number"></ComboBoxItem>
</ComboBox>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
ViewModel:
private DataTable _sampledData = new DataTable();
public DataTable SampledData
{
get => _sampledData;
set { _sampledData = value; NotifyOfPropertyChange(() => SampledData); }
}
Solutions in code behind are welcome too as long as I can pass the mappings to ViewModel later.
EDIT:
I've been trying to make this work with a List of ViewModels, but no luck:
public class ShellViewModel : Screen
{
public List<MyRowViewModel> Rows { get; set; }
public ShellViewModel()
{
Rows = new List<MyRowViewModel>
{
new MyRowViewModel { Column1 = "Test 1", Column2= 1 },
new MyRowViewModel { Column1 = "Test 2", Column2= 2 }
};
}
}
View
<DataGrid ItemsSource="{Binding Rows}">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding ., Mode=OneWay}"/>
<ComboBox ItemsSource="{Binding ??????}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
Row
public class MyRowViewModel : PropertyChangedBase
{
public string Column1 { get; set; }
public int Column2 { get; set; }
}
EDIT2:
To clarify, I need a solution that will handle dynamic number of columns, so some files may store 3 columns and some might store 40 columns. I use this for parsing csv files to later display the data. In order to do that I have to know what types of values the file contains. Because some types may be ambiguous, I let the user decide which types they want. This is identical to Excel's "Load From File" wizard.
The wizard loads a small chunk of data (100 records) and allows user to decide what type the columns are. It automatically parses the columns to:
Let user see how the data will look like
Validate if the column can actually be parsed (e.g. 68.35 cannot
be parsed as DateTime)
Another thing is naming each column. Someone might load csv with each column named C1, C2... but they want to assign meaningful names such as Temperature, Average. This of course has to be parsed later too, because two columns cannot have the same name, but I can take care of this once I have a bindable DataGrid.
Let's break your problem into parts and solve each part separately.
First, the DataGrid itemsource, to make things easier, let's say that our DataGrid has only two columns, column 1 and column 2. A basic model for the DataGrid Items should looks like this:
public class DataGridModel
{
public string FirstProperty { get; set; }
public string SecondProperty { get; set; }
}
Now, assuming that you have a MainWindow (with a ViewModel or the DataContext set to code behind) with a DataGrid in it , let's define DataGridCollection as its ItemSource:
private ObservableCollection<DataGridModel> _dataGridCollection=new ObservableCollection<DataGridModel>()
{
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"},
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"},
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"}
};
public ObservableCollection<DataGridModel> DataGridCollection
{
get { return _dataGridCollection; }
set
{
if (Equals(value, _dataGridCollection)) return;
_dataGridCollection = value;
OnPropertyChanged();
}
}
Second, now the interesting part, the columns structure. Let's define a model for your DataGrid's columns, the model will hold all the required properties to set your DataGrid columns, including:
-DataTypesCollection: a collection that holds the combobox itemsource.
-HeaderPropertyCollection: a collection of Tuples, each Tuple represent a column name and a data type, the data type is basically the selected item of column's combobox.
public class DataGridColumnsModel:INotifyPropertyChanged
{
private ObservableCollection<string> _dataTypesCollection = new ObservableCollection<string>()
{
"Date","String","Number"
};
public ObservableCollection<string> DataTypesCollection
{
get { return _dataTypesCollection; }
set
{
if (Equals(value, _dataTypesCollection)) return;
_dataTypesCollection = value;
OnPropertyChanged();
}
}
private ObservableCollection<Tuple<string, string>> _headerPropertiesCollection=new ObservableCollection<Tuple<string, string>>()
{
new Tuple<string, string>("Column 1", "Date"),
new Tuple<string, string>("Column 2", "String")
}; //The Dictionary has a PropertyName (Item1), and a PropertyDataType (Item2)
public ObservableCollection<Tuple<string,string>> HeaderPropertyCollection
{
get { return _headerPropertiesCollection; }
set
{
if (Equals(value, _headerPropertiesCollection)) return;
_headerPropertiesCollection = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in you MainWindow's viewmodel (or codebehind) define an instance of the DataGridColumnsModel that we will be using to hold our DataGrid structure:
private DataGridColumnsModel _dataGridColumnsModel=new DataGridColumnsModel();
public DataGridColumnsModel DataGridColumnsModel
{
get { return _dataGridColumnsModel; }
set
{
if (Equals(value, _dataGridColumnsModel)) return;
_dataGridColumnsModel = value;
OnPropertyChanged();
}
}
Third, getting the column's TextBox's value. For that w'll be using a MultiBinding and a MultiValueConverter, the first property that w'll be passing to the MultiBinding is the collection of tuples that we define (columns' names and datatypes): HeaderPropertyCollection, the second one is the current column index that w'll retrieve from DisplayIndex using an ancestor binding to the DataGridColumnHeader:
<TextBox >
<TextBox.Text>
<MultiBinding Converter="{StaticResource GetPropertConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}}" Path="DataGridColumnsModel.HeaderPropertyCollection"/>
<Binding Path="DisplayIndex" Mode="OneWay" RelativeSource="{RelativeSource RelativeSource={x:Type DataGridColumnHeader}}"/>
</MultiBinding>
</TextBox.Text>
The converter will simply retrieve the right item using the index from collection of tuples:
public class GetPropertConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
try
{
var theCollection = values[0] as ObservableCollection<Tuple<string, string>>;
return (theCollection?[(int)values[1]])?.Item1; //Item1 is the column name, Item2 is the column's ocmbobox's selectedItem
}
catch (Exception)
{
//use a better implementation!
return null;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Fourth, The last part is to update the DataGrid's ItemSource when the Combobox's selection changed, for that you could use the Interaction tools defined in System.Windows.Interactivity namespace (which is part of Expression.Blend.Sdk, use NuGet to install it: Install-Package Expression.Blend.Sdk):
<ComboBox ItemsSource="{Binding DataGridColumnsModel.DataTypesCollection,RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding UpdateItemSourceCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Each time the selectionChanged event occurred, update your DataGrid's ItemSource in the UpdateItemSourceCommand that should be added to your mainWindow's ViewModel:
private RelayCommand _updateItemSourceCommand;
public RelayCommand UpdateItemSourceCommand
{
get
{
return _updateItemSourceCommand
?? (_updateItemSourceCommand = new RelayCommand(
() =>
{
//Update your DataGridCollection, you could also pass a parameter and use it.
//Update your DataGridCollection based on DataGridColumnsModel.HeaderPropertyCollection
}));
}
}
Ps: the RelayCommand class i am using is part of GalaSoft.MvvmLight.Command namespace, you could add it via NuGet, or define your own command.
Finally here the full xaml code:
Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<local:GetPropertConverter x:Key="GetPropertConverter"/>
</Window.Resources>
<Grid>
<DataGrid x:Name="SampleGrid" ItemsSource="{Binding DataGridCollection}" AutoGenerateColumns="False">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBox >
<TextBox.Text>
<MultiBinding Converter="{StaticResource GetPropertConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type Window}}" Path="DataGridColumnsModel.HeaderPropertyCollection"/>
<Binding Path="DisplayIndex" Mode="OneWay" RelativeSource="{RelativeSource AncestorType={x:Type DataGridColumnHeader}}"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
<ComboBox ItemsSource="{Binding DataGridColumnsModel.DataTypesCollection,RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding UpdateItemSourceCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="First Column" Binding="{Binding FirstProperty}" />
<DataGridTextColumn Header="Second Column" Binding="{Binding SecondProperty}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
And view models / codebehind:
public class GetPropertConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
try
{
var theCollection = values[0] as ObservableCollection<Tuple<string, string>>;
return (theCollection?[(int)values[1]])?.Item1; //Item1 is the column name, Item2 is the column's ocmbobox's selectedItem
}
catch (Exception)
{
//use a better implementation!
return null;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class DataGridColumnsModel:INotifyPropertyChanged
{
private ObservableCollection<string> _dataTypesCollection = new ObservableCollection<string>()
{
"Date","String","Number"
};
public ObservableCollection<string> DataTypesCollection
{
get { return _dataTypesCollection; }
set
{
if (Equals(value, _dataTypesCollection)) return;
_dataTypesCollection = value;
OnPropertyChanged();
}
}
private ObservableCollection<Tuple<string, string>> _headerPropertiesCollection=new ObservableCollection<Tuple<string, string>>()
{
new Tuple<string, string>("Column 1", "Date"),
new Tuple<string, string>("Column 2", "String")
}; //The Dictionary has a PropertyName (Item1), and a PropertyDataType (Item2)
public ObservableCollection<Tuple<string,string>> HeaderPropertyCollection
{
get { return _headerPropertiesCollection; }
set
{
if (Equals(value, _headerPropertiesCollection)) return;
_headerPropertiesCollection = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class DataGridModel
{
public string FirstProperty { get; set; }
public string SecondProperty { get; set; }
}
public partial class MainWindow : Window,INotifyPropertyChanged
{
private RelayCommand _updateItemSourceCommand;
public RelayCommand UpdateItemSourceCommand
{
get
{
return _updateItemSourceCommand
?? (_updateItemSourceCommand = new RelayCommand(
() =>
{
//Update your DataGridCollection, you could also pass a parameter and use it.
MessageBox.Show("Update has ocured");
}));
}
}
private ObservableCollection<DataGridModel> _dataGridCollection=new ObservableCollection<DataGridModel>()
{
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"},
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"},
new DataGridModel(){FirstProperty = "first item",SecondProperty = "second item"}
};
public ObservableCollection<DataGridModel> DataGridCollection
{
get { return _dataGridCollection; }
set
{
if (Equals(value, _dataGridCollection)) return;
_dataGridCollection = value;
OnPropertyChanged();
}
}
private DataGridColumnsModel _dataGridColumnsModel=new DataGridColumnsModel();
public DataGridColumnsModel DataGridColumnsModel
{
get { return _dataGridColumnsModel; }
set
{
if (Equals(value, _dataGridColumnsModel)) return;
_dataGridColumnsModel = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
Update
You will achieve the same result by setting AutoGenerateColumns="True" and creating you columns dynamically.
This is not exactly a complete answer but more a hint towards what I think your looking to do, if so you can query me for additional information.
I think what you want to do is define let say a DataGridColumDef type such as this:
public class DataGridColumnDef : NotifyPropertyChangeModel
{
public string Name
{
get => _Name;
set => SetValue(ref _Name, value);
}
private string _Name;
public Type DataType
{
get => _DataType;
set => SetValue(ref _DataType, value);
}
private Type _DataType;
public DataGridColumnDef(string name, Type type)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
DataType = type ?? throw new ArgumentNullException(nameof(type));
}
}
Then I imagine your view model acting as the data context for the DataGrid could look something like this:
public class MainViewModel : NotifyPropertyChangeModel
{
public ObservableList<DataGridColumnDef> ColumnDefinitions
{
get => _ColumnDefinitions;
set => SetValue(ref _ColumnDefinitions, value);
}
private ObservableList<DataGridColumnDef> _ColumnDefinitions;
public ObservableList<DataGridRowDef> RowDefinitions
{
get => _RowDefinitions;
set => SetValue(ref _RowDefinitions, value);
}
private ObservableList<DataGridRowDef> _RowDefinitions;
public MainViewModel()
{
// Define initial columns
ColumnDefinitions = new ObservableList<DataGridColumnDef>()
{
new DataGridColumnDef("Column 1", typeof(string)),
new DataGridColumnDef("Column 2", typeof(int)),
};
// Create row models from initial column definitions
RowDefinitions = new ObservableList<DataGridRowDef>();
for(int i = 0; i < 100; ++i)
{
RowDefinitions.Add(new DataGridRowDef(ColumnDefinitions));
// OR
//RowDefinitions.Add(new DataGridRowDef(ColumnDefinitions, new object[] { "default", 10 }));
}
}
}
This way on the main view model you could subscribe to collection/property changed events in the ColumnDefinitions property and then re-create the rows collection.
Now the trick that I am not 100% sure would work, but not sure why it wouldn't, is making your DataGridRowDef type inherit from DynamicObject so you can spoof members and their values, something like this:
public class DataGridRowDef : DynamicObject
{
private readonly object[] _columnData;
private readonly IList<DataGridColumnDef> _columns;
public static object GetDefault(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return _columns.Select(c => c.Name).Union(base.GetDynamicMemberNames());
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var columnNames = _columns.Select(c => c.Name).ToList();
if(columnNames.Contains(binder.Name))
{
var columnIndex = columnNames.IndexOf(binder.Name);
result = _columnData[columnIndex];
return true;
}
return base.TryGetMember(binder, out result);
}
public DataGridRowDef(IEnumerable<DataGridColumnDef> columns, object[] columnData = null)
{
_columns = columns.ToList() ?? throw new ArgumentNullException(nameof(columns));
if (columnData == null)
{
_columnData = new object[_columns.Count()];
for (int i = 0; i < _columns.Count(); ++i)
{
_columnData[i] = GetDefault(_columns[i].DataType);
}
}
else
{
_columnData = columnData;
}
}
}
Anyway if this kind of solution seems approachable to you I can try and work through it a bit more possibly.
Try this.
Window1.xaml
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<this:RowDataConverter x:Key="RowDataConverter1" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Rows, Mode=OneWay}">
<DataGrid.Columns>
<DataGridTextColumn>
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource RowDataConverter1}">
<Binding Path="Column1" Mode="OneWay" />
<Binding Path="Column1OptionString" Mode="OneWay" RelativeSource="{RelativeSource AncestorType=Window, Mode=FindAncestor}" />
</MultiBinding>
</DataGridTextColumn.Binding>
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="Column Header 1" />
<ComboBox ItemsSource="{Binding ColumnOptions, Mode=OneWay, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
SelectedValue="{Binding Column1OptionString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}"
SelectedValuePath="Option">
<ComboBox.ItemTemplate>
<DataTemplate DataType="this:ColumnOption">
<TextBlock Text="{Binding Option, Mode=OneTime}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Window1.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication1
{
public partial class Window1 : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public List<RowData> Rows { get; set; }
public List<ColumnOption> ColumnOptions { get; set; }
private string _column1OptionString;
public string Column1OptionString
{
get
{
return _column1OptionString;
}
set
{
_column1OptionString = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Column1OptionString"));
}
}
public Window1()
{
InitializeComponent();
ColumnOptions = new List<ColumnOption>() {
new ColumnOption(){ Option = "String", StringFormat = "" },
new ColumnOption(){ Option = "Int32", StringFormat = "" }
};
Rows = new List<RowData>() {
new RowData(){ Column1 = "01234" }
};
_column1OptionString = "String";
this.DataContext = this;
}
}
public class ColumnOption
{
public string Option { get; set; }
public string StringFormat { get; set; }
}
public class RowData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private object _column1;
public object Column1
{
get
{
return _column1;
}
set
{
_column1 = value;
if (PropertyChanged!= null)
PropertyChanged(this, new PropertyChangedEventArgs("Column1"));
}
}
}
public class RowDataConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[1] == null)
return values[0].ToString();
switch (values[1].ToString())
{
case "String":
return values[0].ToString();
case "Int32":
Int32 valueInt;
Int32.TryParse(values[0].ToString(), out valueInt);
return valueInt.ToString();
default:
return values[0].ToString();
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
UPDATE
based on #FCin comment
"This is nice, but I use this to load csv files and the number of columns changes depending of csv file. Here I have to hardcore each column, but some files may have 1 column and some might have 30 columns".
Assume your csv file using format:
line1: Headers,
line2: Data Type,
line3-end: Records.
Example data1.csv:
ColumnHeader1,ColumnHeader2
Int32,String
1,"A"
2,"B"
3,"C"
I try to parse csv file using TextFieldParser, then generate the DataGrid's columns programmatically.
Window2.xaml
<Window x:Class="WpfApplication1.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<Label Content="File:" />
<ComboBox x:Name="FileOption"
SelectionChanged="FileOption_SelectionChanged">
<ComboBox.Items>
<Run Text="Data1.csv" />
<Run Text="Data2.csv" />
</ComboBox.Items>
</ComboBox>
</StackPanel>
<DataGrid x:Name="DataGrid1" Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding ListOfRecords, Mode=OneWay}">
</DataGrid>
</Grid>
</Window>
Window2.xaml.cs
using Microsoft.VisualBasic.FileIO;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
namespace WpfApplication1
{
public partial class Window2 : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
List<myDynamicObject> _listOfRecords;
public List<myDynamicObject> ListOfRecords
{
get
{
return _listOfRecords;
}
}
public Window2()
{
InitializeComponent();
DataContext = this;
}
public void LoadData(string fileName)
{
_listOfRecords = new List<myDynamicObject>();
myDynamicObject record;
TextFieldParser textFieldParser = new TextFieldParser(fileName);
textFieldParser.TextFieldType = FieldType.Delimited;
textFieldParser.SetDelimiters(",");
string[] headers = null;
string[] dataTypes = null;
string[] fields;
int i = 0;
while(!textFieldParser.EndOfData)
{
fields = textFieldParser.ReadFields();
if (i == 0)
{
headers = fields;
}
else if (i == 1)
{
dataTypes = fields;
}
else
{
record = new myDynamicObject();
for (int j = 0; j < fields.Length; j++)
{
switch(dataTypes[j].ToLower())
{
case "string":
record.SetMember(headers[j], fields[j]);
break;
case "int32":
Int32 data;
if (Int32.TryParse(fields[j], out data))
{
record.SetMember(headers[j], data);
}
break;
default:
record.SetMember(headers[j], fields[j]);
break;
}
}
_listOfRecords.Add(record);
}
i += 1;
}
PropertyChanged(this, new PropertyChangedEventArgs("ListOfRecords"));
DataGrid1.Columns.Clear();
for (int j = 0; j < headers.Length; j++)
{
DataGrid1.Columns.Add(new DataGridTextColumn()
{
Header = headers[j],
Binding = new Binding()
{
Path = new PropertyPath(headers[j]),
Mode = BindingMode.OneWay
}
});
}
}
private void FileOption_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
LoadData((FileOption.SelectedItem as Run).Text);
}
}
public class myDynamicObject : DynamicObject
{
Dictionary<string, object> dictionary = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name;
return dictionary.TryGetValue(name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
dictionary[binder.Name] = value;
return true;
}
public void SetMember(string propertyName, object value)
{
dictionary[propertyName] = value;
}
}
}

ICommand interface not works

I have problem with navigation after I have added ICommand interface. Button is still disable and I don't why. For me everythings looks good but if I type in text into Textbox. Maybe binding doesn't work and i don't have value on out. I have tried figure out what's wrong with that code but I don't have idea
RegisterVM
public class RegisterVM : INotifyPropertyChanged
{
// string connetionString = null;
// string sql = null;
private string _social;
public string Social
{
get { return _social; }
set
{
_social = value;
Users = new Users()
{
Name = this.Name,
Surname = this.Surname,
Social = this.Social,
};
OnPropertyChanged("Social");
}
}
private string _surname;
public string Surname
{
get { return _surname; }
set
{
_surname = value;
Users = new Users()
{
Name = this.Name,
Surname = this.Surname,
Social = this.Social,
};
OnPropertyChanged("Surname");
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
Users = new Users()
{
Name = this.Name,
Surname = this.Surname,
Social = this.Social,
};
OnPropertyChanged("Name");
}
}
private Users _user;
public Users Users
{
get { return _user; }
set
{
_user = value;
OnPropertyChanged("Users");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public NavigationCandidates navigationCandidates { get; set; }
private readonly MainWindow _mainWindow;
public RegisterVM(MainWindow mainWindow)
{
_mainWindow = mainWindow;
navigationCandidates = new NavigationCandidates(this);
}
public void Navigate()
{
_mainWindow.Content = new Candidates();
_mainWindow.name.Text = string.Empty;
_mainWindow.Surname.Text = string.Empty;
_mainWindow.SearchTermTextBox.Text = string.Empty;
}
public void Register(Users user)
{
Users.Register(user);
}
}
}
Users
public class Users : INotifyPropertyChanged
{
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private string _surname;
public string Surname
{
get { return _surname; }
set
{
_surname = value;
OnPropertyChanged("Surname");
}
}
private string _social;
public string Social
{
get { return _social; }
set
{
_social = value;
OnPropertyChanged("Social");
}
}
private bool _voted;
public bool Voted
{
get { return _voted; }
set
{
_voted = value;
OnPropertyChanged("Voted");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string connetionString = null;
string sql = null;
public void Register(Users User)
{
connetionString = "Data Source=(LocalDB)\\MSSQLLocalDB;AttachDbFilename=C:\\Users\\Anon\\Documents\\Db.mdf;Integrated Security=True;Connect Timeout=30";
using (SqlConnection cnn = new SqlConnection(connetionString))
{
sql = "insert into Table ([Name],[Surname],[Social]values(#name,#surname,#social)";
cnn.Open();
using (SqlCommand cmd = new SqlCommand(sql, cnn))
{
cmd.Parameters.AddWithValue("#name", User.Name);
cmd.Parameters.AddWithValue("#surname", User.Surname);
cmd.Parameters.AddWithValue("#social", User.Social);
cmd.ExecuteNonQuery();
}
}
}
}
Navigaton
public class NavigationCandidates : ICommand
{
// public CandidatesVM candidatesVM { get; set; }
public RegisterVM regVM { get; set; }
public event EventHandler CanExecuteChanged;
/* public NavigationCandidates(CandidatesVM canVM)
{
candidatesVM = canVM;
}*/
public NavigationCandidates(RegisterVM regiVM)
{
regVM = regiVM;
}
public bool CanExecute(object parameter)
{
Users user = (Users)parameter;
if (user != null)
{
if (string.IsNullOrEmpty(user.Name) || string.IsNullOrEmpty(user.Surname) || string.IsNullOrEmpty(user.Social))
return false;
return true;
}
return false;
}
public void Execute(object parameter)
{
Users user = (Users)parameter;
regVM.Register(user);
regVM.Navigate();
// candidatesVM.Input();
// candidatesVM.Navigate();
// candidatesVM.DeSerializationCamera();
}
}
XAML
<Window.Content>
<Border Background="#202021" CornerRadius="40">
<StackPanel Margin="20">
<Label Content="Type in your data" Foreground="White" HorizontalAlignment="Center" FontSize="20" FontWeight="Bold"/>
<Separator/>
<Label Content="Name" HorizontalAlignment="Center" Foreground="White" FontWeight="Bold"/>
<TextBox HorizontalAlignment="Center" MinWidth="320" MinHeight="32" Foreground="Black" Background="#f5ddff" FontSize="15" FontWeight="DemiBold"
TextAlignment="Center" Margin="0,0,0,10" x:Name="name" Padding="0,4,0,-2" Text="{Binding Name, Mode=TwoWay}" MaxWidth="320" MaxLength="40" PreviewTextInput="SignValidationTextBox"/>
<TextBlock IsHitTestVisible="False" Text="Enter Name" HorizontalAlignment="Center" Margin="0,-35,0,0" Foreground="Black" >
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=name}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Label Content="Surname" HorizontalAlignment="Center" Foreground="White" FontWeight="Bold"/>
<TextBox HorizontalAlignment="Center" MinWidth="320" MinHeight="32" Foreground="Black" Background="#f5ddff" FontSize="15" FontWeight="DemiBold" Margin="0,0,0,10"
x:Name="Surname" TextAlignment="Center" Padding="0,4,0,-2" MaxHeight="320" Text="{Binding Surname, Mode=TwoWay}" MaxLength="40" PreviewTextInput="SignValidationTextBox" MaxWidth="320"/>
<TextBlock IsHitTestVisible="False" Text="Enter Surname" HorizontalAlignment="Center" Margin="0,-35,0,0" Foreground="Black">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=Surname}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Label Content="Social" HorizontalAlignment="Center" Foreground="White" FontWeight="Bold"/>
<TextBox HorizontalAlignment="Center" MinWidth="320" MinHeight="32" Foreground="Black" Background="#f5ddff" FontSize="15" FontWeight="DemiBold" MaxLength="11" Margin="0,0,0,10"
x:Name="SearchTermTextBox" PreviewTextInput="NumberValidationTextBox" Text="{Binding Social, Mode=TwoWay}" TextAlignment="Center" Padding="0,4,0,-2" />
<TextBlock IsHitTestVisible="False" Text="Enter Social" HorizontalAlignment="Center" Margin="0,-35,0,0" Foreground="Black" >
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=SearchTermTextBox}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Separator Margin="0,10,0,0"/>
<Button HorizontalAlignment="Center" MinHeight="32" MinWidth="120" Foreground="Black" Background="White" Margin="0,45,0,0" Content="Login" FontWeight="Bold" BorderThickness="2"
x:Name="BtnClk" Command="{Binding navigationCandidates}" CommandParameter="{Binding Users}"/>
</StackPanel>
</Border>
</Window.Content>
MainPage
public partial class MainWindow : NavigationWindow
{
RegisterVM registerVM;
public MainWindow()
{
InitializeComponent();
registerVM = new RegisterVM(this);
DataContext = registerVM;
}
}
Your problem is here:
public NavigationCandidates navigationCandidates { get; set; }
public RegisterVM(MainWindow mainWindow)
{
_mainWindow = mainWindow;
navigationCandidates = new NavigationCandidates(this);
}
You bind your button to a property that doesn't notify.
TextBox Binding isn't wrong , Only thing that I can't see in your code is ICommand Binding in RegisterVM.
Implement RelayCommand Class in your code and then
add Icommand property in you RegisterVM like this.
private ICommand _navigationCandidates;
public ICommand NavigationCandidates
{
get
{
return _navigationCandidates ?? (_navigationCandidates = new RelayCommand(OnnavigationClicked));
}
}
private void OnnavigationClicked()
{
// throw new NotImplementedException();
}
Ok, here an example
1 - Create a RelayCommand Class
//necessary namespaces
using System;
using System.Diagnostics;
using System.Windows.Input;
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#region Constructors
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
//For Vs 2015+
return _canExecute?.Invoke(parameter) ?? true;
}
/// <summary>
/// Can execute changed event handler.
/// </summary>
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
2 - Create a ViewModelBase (preferably abstract class), implement INotifyPropertyChanged
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public virtual ICommand NavigateCommand => new RelayCommand(Navigate, o=> CanExecute());
//You'll override this method in your class
protected virtual bool CanExecute()
{
return true;
}
//You'll override this method in your class
protected virtual void Navigate(object param)
{
}
}
3 - Your ViewModel.
public class ViewModel: ViewModelBase
{
protected override Navigate(object param)
{
//here you navigate
}
protected override bool CanExecute()
{
var result = false;
//do whatever you want to return true or false;
return result;
}
}
note: - you are mixing MVVM with code behind, you could not for example set the values of your TextBoxes directly in the Navigate method.
I suggest you read materials regarding MVVM Pattern
Add a method to your NavigationCandidates class that raises the CanExecuteChanged event:
public class NavigationCandidates : ICommand
{
public RegisterVM regVM { get; set; }
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
public NavigationCandidates(RegisterVM regiVM)
{
regVM = regiVM;
}
public bool CanExecute(object parameter)
{
Users user = (Users)parameter;
if (user != null)
{
if (string.IsNullOrEmpty(user.Name) || string.IsNullOrEmpty(user.Surname) || string.IsNullOrEmpty(user.Social))
return false;
return true;
}
return false;
}
public void Execute(object parameter)
{
Users user = (Users)parameter;
regVM.Register(user);
regVM.Navigate();
}
}
...and call this method whenever the Users is set in the RegisterVM class:
public Users Users
{
get { return _user; }
set
{
_user = value;
OnPropertyChanged("Users");
navigationCandidates.RaiseCanExecuteChanged();
}
}
You may also want to set the UpdateSourceTrigger property of the bindings in the view to PropertyChanged for the Button to become enabled as soon as you have typed something into all three TextBoxes:
Text="{Binding Social, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

WPF Bind command property from selected item in observablecollection

I am using Listview having datasource as observablecollection<empclass>
My overall class structure is like
Class empclass
{
command = new RelayCommand(myfunction, true);
private int _abc;
public int abc
{
get { return _abc;}
set { _abc = value;
onpropertychanged("abc")
}
private int _pqr;
public int pqr
{
get { return _pqr;}
set { _pqr = value;
onpropertychanged("pqr")
}
public void myfunction()
{
messagebox.show((abc+pqr).Tostring());
}
}
i have a separate button, where on click i want to invoke command of selected item to show addition of abc and pqr on respected values present in that object.
It would be great If you could help me with small code example.
Thanks
Ashpak
I'm assuming you have a ListView named lv:
<ListView Name="lv" ... </ListView>
Then you can bind to the SelectedItem of that ListView and use the item's command property.
<Button Command="{Binding ElementName=lv, Path=SelectedItem.command}">Button Text</Button>
Note that for this to work you have to have a public property command, e.g.:
Class empclass
{
public RelayCommand command {get;set;}
...
}
Try this:
1. XAML code:
<Window x:Class="SoButtonBindingHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:soButtonBindingHelpAttempt="clr-namespace:SoButtonBindingHelpAttempt"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<soButtonBindingHelpAttempt:MainViewModel/>
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding ObservableCollection}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type soButtonBindingHelpAttempt:Empclass}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"></ColumnDefinition>
<ColumnDefinition Width="120"></ColumnDefinition>
<ColumnDefinition Width="120"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding abc, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBlock>
<TextBlock Grid.Column="1" Text="{Binding pqr, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBlock>
<Button Grid.Column="2" Command="{Binding Command}" Content="Press me!"></Button>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid></Window>
2. ViewModel code Model code:
public class MainViewModel:BaseObservableObject
{
public MainViewModel()
{
ObservableCollection = new ObservableCollection<Empclass>(new List<Empclass>
{
new Empclass{abc=2, pqr = 3},
new Empclass{abc=5, pqr = 7},
new Empclass{abc=11, pqr = 13},
new Empclass{abc=17, pqr = 19}
});
}
public ObservableCollection<Empclass> ObservableCollection { get; set; }
}
public class Empclass : BaseObservableObject
{
private ICommand _command;
private int _abc;
private int _pqr;
public ICommand Command
{
get { return _command ?? (_command = new RelayCommand(myfunction)); }
}
public int abc
{
get { return _abc; }
set
{
_abc = value;
OnPropertyChanged("abc");
}
}
public int pqr
{
get { return _pqr; }
set
{
_pqr = value;
OnPropertyChanged("pqr");
}
}
private void myfunction()
{
//add you command logic here
var temp = pqr;
pqr = abc;
abc = temp;
}
}
3. MVVM parts implementation:
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
public class RelayCommand<T> : ICommand
{
readonly Action<T> _execute;
readonly Func<T, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public void RefreshCommand()
{
var cec = CanExecuteChanged;
if (cec != null)
cec(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
{
if (_canExecute == null) return true;
return _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
}
public class RelayCommand : RelayCommand<object>
{
public RelayCommand(Action execute, Func<bool> canExecute = null)
: base(_ => execute(),
_ => canExecute == null || canExecute())
{
}
}
4. Look like this:
Regards

Modern UI for WPF - ModernDialog: Allow to click Ok only on some conditions

I started a new small project, using ModernUI for WPF.
I've to display a Dialog with only two textbox and two button(ok/cancel).
The two textbox have some validation(one is a name that should be >0, the other should be an email).
Currently I've the following:
var userEditionForm = new UserEditionForm(oneUserConfigurationViewModel);
var dlg = new ModernDialog
{
Title = "User edition",
Content = userEditionForm,
Width = 300
};
dlg.Buttons = new[] {dlg.OkButton, dlg.CancelButton};
dlg.ShowDialog();
return dlg.DialogResult.HasValue && dlg.DialogResult.Value;
The content user control is:
<UserControl x:Class="Test.UserControls.UserEditionForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="StackPanel">
<Setter Property="Orientation" Value="Horizontal" />
<Setter Property="Margin" Value="0,0,0,4" />
</Style>
<Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
<Setter Property="Width" Value="100" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</StackPanel.Resources>
<StackPanel>
<Label Content="Name" Target="{Binding ElementName=TextFirstName}" />
<TextBox x:Name="TextFirstName" Width="150"
Text="{Binding User.Name, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</StackPanel>
<StackPanel>
<Label Content="Email" Target="{Binding ElementName=TextEmail}" />
<TextBox x:Name="TextEmail" Width="150"
Text="{Binding User.Email, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
The validation is made, but I would like to prevent the user to click "OK" until the validation is good. Is it possible? If yes, how?
This was actually pretty tricky but it worked for me:
ViewModel
public class UserInfo : INotifyPropertyChanged, IDataErrorInfo
{
private static Regex emailRegex = new Regex(#"^\w+(?:\.\w+)*?#[a-zA-Z_]+?\.[a-zA-Z]{2,3}$");
private string firstname;
private string email;
public string FirstName
{
get { return firstname; }
set { firstname = value; OnPropertyChanged(); }
}
public string EMail
{
get { return email; }
set { email = value; OnPropertyChanged(); }
}
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "FirstName":
return string.IsNullOrWhiteSpace(FirstName) ? "Firstname is required!" : null;
case "EMail":
return EMail == null || !emailRegex.IsMatch(EMail) ? "The Email Address is not valid!" : null;
default:
return null;
}
}
}
public LoginCommand LoginCommand { get; private set; }
public UserInfo()
{
LoginCommand = new LoginCommand(this);
}
private void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
Command (No clue what you want to call it...)
public class LoginCommand : ICommand
{
private UserInfo info;
private ICommand attachedCommand;
public bool CanExecute(object parameter)
{
return parameter != null && info["FirstName"] == null && info["EMail"] == null;
}
public event EventHandler CanExecuteChanged = delegate { };
public void Execute(object parameter)
{
Debug.WriteLine("This Works!");
//Add execution logic here
if (attachedCommand != null)
{
attachedCommand.Execute(parameter); //should close the window
}
}
private void Info_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
CanExecuteChanged(this, new EventArgs());
}
public LoginCommand(UserInfo info)
{
this.info = info;
info.PropertyChanged += Info_PropertyChanged;
}
public void AttachCommand(ICommand command) //attach the original command here
{
attachedCommand = command;
}
}
Usage with ModernUI
UserInfo info = new UserInfo();
UserInfoForm form = new UserInfoForm(info);
ModernDialog dialog = new ModernDialog
{
Title = "User edition",
Content = form,
Width = 300
};
Button btnOk = dialog.OkButton;
ICommand originalCommand = btnOk.Command; //the original command should close the window so i keep it
btnOk.Command = info.LoginCommand;
info.LoginCommand.AttachCommand(originalCommand); //and attach it to my command
dialog.Buttons = new[] { btnOk, dialog.CancelButton };
dialog.ShowDialog();

Categories

Resources