I have 3 TextBoxes (Id1,Name and Salary). Id and Salary should contain integers and Name should only contain characters. I need validations for my TextBox, it should show errors as I enter wrong characters or integers. Also can this be done only in Xaml without codebehind? Please help me with the required code
This is Xaml code:
<TextBox Name="tb1" HorizontalAlignment="Left" Height="20" Margin="60,10,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Id,ElementName=dgsample}" VerticalAlignment="Top" Width="100" />
<TextBox Name="tb2" HorizontalAlignment="Left" Height="20" Margin="60,60,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Name, ElementName=dgsample}" VerticalAlignment="Top" Width="100"/>
<TextBox Name="tb3" HorizontalAlignment="Left" Height="20" Margin="60,110,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Salary, ElementName=dgsample}" VerticalAlignment="Top" Width="100"/>
There a 3 ways to implement validation:
Validation Rule
Implementation of INotifyDataErrorInfo
Implementation of IDataErrorInfo
Validation rule example:
public class NumericValidationRule : ValidationRule
{
public Type ValidationType { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string strValue = Convert.ToString(value);
if (string.IsNullOrEmpty(strValue))
return new ValidationResult(false, $"Value cannot be coverted to string.");
bool canConvert = false;
switch (ValidationType.Name)
{
case "Boolean":
bool boolVal = false;
canConvert = bool.TryParse(strValue, out boolVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of boolean");
case "Int32":
int intVal = 0;
canConvert = int.TryParse(strValue, out intVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Int32");
case "Double":
double doubleVal = 0;
canConvert = double.TryParse(strValue, out doubleVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Double");
case "Int64":
long longVal = 0;
canConvert = long.TryParse(strValue, out longVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Int64");
default:
throw new InvalidCastException($"{ValidationType.Name} is not supported");
}
}
}
XAML:
Very important: don't forget to set ValidatesOnTargetUpdated="True" it won't work without this definition.
<TextBox x:Name="Int32Holder"
IsReadOnly="{Binding IsChecked,ElementName=CheckBoxEditModeController,Converter={converters:BooleanInvertConverter}}"
Style="{StaticResource ValidationAwareTextBoxStyle}"
VerticalAlignment="Center">
<!--Text="{Binding Converter={cnv:TypeConverter}, ConverterParameter='Int32', Path=ValueToEdit.Value, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"-->
<TextBox.Text>
<Binding Path="Name"
Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged"
Converter="{cnv:TypeConverter}"
ConverterParameter="Int32"
ValidatesOnNotifyDataErrors="True"
ValidatesOnDataErrors="True"
NotifyOnValidationError="True">
<Binding.ValidationRules>
<validationRules:NumericValidationRule ValidationType="{x:Type system:Int32}"
ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<!--NumericValidationRule-->
</TextBox>
INotifyDataErrorInfo example:
public abstract class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
ValidateAsync();
}
#endregion
public virtual void OnLoaded()
{
}
#region INotifyDataErrorInfo
private ConcurrentDictionary<string, List<string>> _errors = new ConcurrentDictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void OnErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
{
List<string> errorsForName;
_errors.TryGetValue(propertyName, out errorsForName);
return errorsForName;
}
public bool HasErrors
{
get { return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0); }
}
public Task ValidateAsync()
{
return Task.Run(() => Validate());
}
private object _lock = new object();
public void Validate()
{
lock (_lock)
{
var validationContext = new ValidationContext(this, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(this, validationContext, validationResults, true);
foreach (var kv in _errors.ToList())
{
if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
{
List<string> outLi;
_errors.TryRemove(kv.Key, out outLi);
OnErrorsChanged(kv.Key);
}
}
var q = from r in validationResults
from m in r.MemberNames
group r by m into g
select g;
foreach (var prop in q)
{
var messages = prop.Select(r => r.ErrorMessage).ToList();
if (_errors.ContainsKey(prop.Key))
{
List<string> outLi;
_errors.TryRemove(prop.Key, out outLi);
}
_errors.TryAdd(prop.Key, messages);
OnErrorsChanged(prop.Key);
}
}
}
#endregion
}
View Model Implementation:
public class MainFeedViewModel : BaseViewModel//, IDataErrorInfo
{
private ObservableCollection<FeedItemViewModel> _feedItems;
[XmlIgnore]
public ObservableCollection<FeedItemViewModel> FeedItems
{
get
{
return _feedItems;
}
set
{
_feedItems = value;
OnPropertyChanged("FeedItems");
}
}
[XmlIgnore]
public ObservableCollection<FeedItemViewModel> FilteredFeedItems
{
get
{
if (SearchText == null) return _feedItems;
return new ObservableCollection<FeedItemViewModel>(_feedItems.Where(x => x.Title.ToUpper().Contains(SearchText.ToUpper())));
}
}
private string _title;
[Required]
[StringLength(20)]
//[CustomNameValidationRegularExpression(5, 20)]
[CustomNameValidationAttribute(3, 20)]
public string Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged("Title");
}
}
private string _url;
[Required]
[StringLength(200)]
[Url]
//[CustomValidation(typeof(MainFeedViewModel), "UrlValidation")]
/// <summary>
/// Validation of URL should be with custom method like the one that implemented below, or with
/// </summary>
public string Url
{
get { return _url; }
set
{
_url = value;
OnPropertyChanged("Url");
}
}
public MainFeedViewModel(string url, string title)
{
Title = title;
Url = url;
}
/// <summary>
///
/// </summary>
public MainFeedViewModel()
{
}
public MainFeedViewModel(ObservableCollection<FeedItemViewModel> feeds)
{
_feedItems = feeds;
}
private string _searchText;
[XmlIgnore]
public string SearchText
{
get { return _searchText; }
set
{
_searchText = value;
OnPropertyChanged("SearchText");
OnPropertyChanged("FilteredFeedItems");
}
}
#region Data validation local
/// <summary>
/// Custom URL validation method
/// </summary>
/// <param name="obj"></param>
/// <param name="context"></param>
/// <returns></returns>
public static ValidationResult UrlValidation(object obj, ValidationContext context)
{
var vm = (MainFeedViewModel)context.ObjectInstance;
if (!Uri.IsWellFormedUriString(vm.Url, UriKind.Absolute))
{
return new ValidationResult("URL should be in valid format", new List<string> { "Url" });
}
return ValidationResult.Success;
}
#endregion
}
XAML:
<UserControl x:Class="RssReaderTool.Views.AddNewFeedDialogView"
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">
<FrameworkElement.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="TextErrorTemplate">
<DockPanel LastChildFill="True">
<AdornedElementPlaceholder>
<Border BorderBrush="Red"
BorderThickness="2" />
</AdornedElementPlaceholder>
<TextBlock FontSize="20"
Foreground="Red">*?*</TextBlock>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource=
{x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
<!--<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>-->
</FrameworkElement.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Feed Name"
ToolTip="Display" />
<TextBox Text="{Binding MainFeedViewModel.Title,UpdateSourceTrigger=PropertyChanged,ValidatesOnNotifyDataErrors=True,ValidatesOnDataErrors=True}"
Grid.Column="2" />
<TextBlock Text="Feed Url"
Grid.Row="2" />
<TextBox Text="{Binding MainFeedViewModel.Url,UpdateSourceTrigger=PropertyChanged,ValidatesOnNotifyDataErrors=True,ValidatesOnDataErrors=True}"
Grid.Column="2"
Grid.Row="2" />
</Grid>
</UserControl>
IDataErrorInfo:
View Model:
public class OperationViewModel : ViewModelBase, IDataErrorInfo
{
private const int ConstCodeMinValue = 1;
private readonly IEventAggregator _eventAggregator;
private OperationInfoDefinition _operation;
private readonly IEntityFilterer _contextFilterer;
private OperationDescriptionViewModel _description;
public long Code
{
get { return _operation.Code; }
set
{
if (SetProperty(value, _operation.Code, o => _operation.Code = o))
{
UpdateDescription();
}
}
}
public string Description
{
get { return _operation.Description; }
set
{
if (SetProperty(value, _operation.Description, o => _operation.Description = o))
{
UpdateDescription();
}
}
}
public string FriendlyName
{
get { return _operation.FriendlyName; }
set
{
if (SetProperty(value, _operation.FriendlyName, o => _operation.FriendlyName = o))
{
UpdateDescription();
}
}
}
public int Timeout
{
get { return _operation.Timeout; }
set
{
if (SetProperty(value, _operation.Timeout, o => _operation.Timeout = o))
{
UpdateDescription();
}
}
}
public string Category
{
get { return _operation.Category; }
set
{
if (SetProperty(value, _operation.Category, o => _operation.Category = o))
{
UpdateDescription();
}
}
}
public bool IsManual
{
get { return _operation.IsManual; }
set
{
if (SetProperty(value, _operation.IsManual, o => _operation.IsManual = o))
{
UpdateDescription();
}
}
}
void UpdateDescription()
{
//some code
}
#region Validation
#region IDataErrorInfo
public ValidationResult Validate()
{
return ValidationService.Instance.ValidateNumber(Code, ConstCodeMinValue, long.MaxValue);
}
public string this[string columnName]
{
get
{
var validation = ValidationService.Instance.ValidateNumber(Code, ConstCodeMinValue, long.MaxValue);
return validation.IsValid ? null : validation.ErrorContent.ToString();
}
}
public string Error
{
get
{
var result = Validate();
return result.IsValid ? null : result.ErrorContent.ToString();
}
}
#endregion
#endregion
}
XAML:
<controls:NewDefinitionControl x:Class="DiagnosticsDashboard.EntityData.Operations.Views.NewOperationView"
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:views="clr-namespace:DiagnosticsDashboard.EntityData.Operations.Views"
xmlns:controls="clr-namespace:DiagnosticsDashboard.Core.Controls;assembly=DiagnosticsDashboard.Core"
xmlns:c="clr-namespace:DiagnosticsDashboard.Core.Validation;assembly=DiagnosticsDashboard.Core"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Column="0"
Grid.Row="0"
Margin="5">Code:</Label>
<Label Grid.Column="0"
Grid.Row="1"
Margin="5">Description:</Label>
<Label Grid.Column="0"
Grid.Row="2"
Margin="5">Category:</Label>
<Label Grid.Column="0"
Grid.Row="3"
Margin="5">Friendly Name:</Label>
<Label Grid.Column="0"
Grid.Row="4"
Margin="5">Timeout:</Label>
<Label Grid.Column="0"
Grid.Row="5"
Margin="5">Is Manual:</Label>
<TextBox Grid.Column="1"
Text="{Binding Code,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Grid.Row="0"
Margin="5"/>
<TextBox Grid.Column="1"
Grid.Row="1"
Margin="5"
Text="{Binding Description}" />
<TextBox Grid.Column="1"
Grid.Row="2"
Margin="5"
Text="{Binding Category}" />
<TextBox Grid.Column="1"
Grid.Row="3"
Margin="5"
Text="{Binding FriendlyName}" />
<TextBox Grid.Column="1"
Grid.Row="4"
Margin="5"
Text="{Binding Timeout}" />
<CheckBox Grid.Column="1"
Grid.Row="5"
Margin="5"
IsChecked="{Binding IsManual}"
VerticalAlignment="Center" />
</Grid>
</controls:NewDefinitionControl>
You can additionally implement IDataErrorInfo as follows in the view model. If you implement IDataErrorInfo, you can do the validation in that instead of the setter of a particular property, then whenever there is a error, return an error message so that the text box which has the error gets a red box around it, indicating an error.
class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string m_Name = "Type Here";
public ViewModel()
{
}
public string Name
{
get
{
return m_Name;
}
set
{
if (m_Name != value)
{
m_Name = value;
OnPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Error
{
get { return "...."; }
}
/// <summary>
/// Will be called for each and every property when ever its value is changed
/// </summary>
/// <param name="columnName">Name of the property whose value is changed</param>
/// <returns></returns>
public string this[string columnName]
{
get
{
return Validate(columnName);
}
}
private string Validate(string propertyName)
{
// Return error message if there is error on else return empty or null string
string validationMessage = string.Empty;
switch (propertyName)
{
case "Name": // property name
// TODO: Check validiation condition
validationMessage = "Error";
break;
}
return validationMessage;
}
}
And you have to set ValidatesOnDataErrors=True in the XAML in order to invoke the methods of IDataErrorInfo as follows:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
To get it done only with XAML you need to add Validation Rules for individual properties. But i would recommend you to go with code behind approach.
In your code, define your specifications in properties setters and throw exceptions when ever it doesn't compliance to your specifications.
And use error template to display your errors to user in UI.
Your XAML will look like this
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="CustomTextBoxTextStyle" TargetType="TextBox">
<Setter Property="Foreground" Value="Green" />
<Setter Property="MaxLength" Value="40" />
<Setter Property="Width" Value="392" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Trigger.Setters>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="Red"/>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox Name="tb2" Height="30" Width="400"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"
Style="{StaticResource CustomTextBoxTextStyle}"/>
</Grid>
Code Behind:
public partial class MainWindow : Window
{
private ExampleViewModel m_ViewModel;
public MainWindow()
{
InitializeComponent();
m_ViewModel = new ExampleViewModel();
DataContext = m_ViewModel;
}
}
public class ExampleViewModel : INotifyPropertyChanged
{
private string m_Name = "Type Here";
public ExampleViewModel()
{
}
public string Name
{
get
{
return m_Name;
}
set
{
if (String.IsNullOrEmpty(value))
{
throw new Exception("Name can not be empty.");
}
if (value.Length > 12)
{
throw new Exception("name can not be longer than 12 charectors");
}
if (m_Name != value)
{
m_Name = value;
OnPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have implemented this validation. But you would be used code behind. It is too much easy and simplest way.
XAML:
For name Validtion only enter character from A-Z and a-z.
<TextBox x:Name="first_name_texbox" PreviewTextInput="first_name_texbox_PreviewTextInput" > </TextBox>
Code Behind.
private void first_name_texbox_PreviewTextInput ( object sender, TextCompositionEventArgs e )
{
Regex regex = new Regex ( "[^a-zA-Z]+" );
if ( regex.IsMatch ( first_name_texbox.Text ) )
{
MessageBox.Show("Invalid Input !");
}
}
For Salary and ID validation, replace regex constructor passed value with [0-9]+. It means you can only enter number from 1 to infinite.
You can also define length with [0-9]{1,4}. It means you can only enter less then or equal to 4 digit number. This baracket means {at least,How many number}. By doing this you can define range of numbers in textbox.
May it help to others.
XAML:
Code Behind.
private void salary_texbox_PreviewTextInput ( object sender, TextCompositionEventArgs e )
{
Regex regex = new Regex ( "[^0-9]+" );
if ( regex.IsMatch ( salary_texbox.Text ) )
{
MessageBox.Show("Invalid Input !");
}
}
When it comes to Muhammad Mehdi's answer, it is better to do:
private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex ( "[^0-9]+" );
if(regex.IsMatch(e.Text))
{
MessageBox.Show("Error");
}
}
Because when comparing with the TextCompositionEventArgs it gets also the last character, while with the textbox.Text it does not. With textbox, the error will show after next inserted character.
When I needed to do this, I followed Microsoft's example using Binding.ValidationRules and it worked first time.
See their article, How to: Implement Binding Validation:
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-binding-validation?view=netframeworkdesktop-4.8
When it comes to DiSaSteR's answer, I noticed a different behavior. textBox.Text shows the text in the TextBox as it was before the user entered a new character, while e.Text shows the single character the user just entered. One challenge is that this character might not get appended to the end, but it will be inserted at the carret position:
private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e){
Regex regex = new Regex ( "[^0-9]+" );
string text;
if (textBox.CaretIndex==textBox.Text.Length) {
text = textBox.Text + e.Text;
} else {
text = textBox.Text.Substring(0, textBox.CaretIndex) + e.Text + textBox.Text.Substring(textBox.CaretIndex);
}
if(regex.IsMatch(text)){
MessageBox.Show("Error");
}
}
Related
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 need a simple way to validate of text boxes (Required Field). It should check all mandatory field existence , when user press button.
I have tried this code :
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="25" Text="*" DockPanel.Dock="Right" />
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
</Window.Resources>
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Height="26" Margin="62,213,0,0" VerticalAlignment="Top" Width="121" Click="Button_Click_1"/>
<TextBox x:Name="txtEmail1" Text="" Height="61" Margin="116,10,194,0" Validation.ErrorTemplate="{StaticResource validationTemplate}"/>
</Grid>
please anyone suggest a way to make validation in Text boxes in WPF.
Thank you
You should bind the Text property of the TextBox to a property of a view model and implement the IDataErrorInfo interface in the view model class.
Please refer to the following sample code.
Code:
public partial class Window3 : Window
{
Window3ViewModel viewModel = new Window3ViewModel();
public Window3()
{
InitializeComponent();
DataContext = viewModel;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
viewModel.Validate();
}
}
public class Window3ViewModel : INotifyDataErrorInfo
{
private readonly Dictionary<string, string> _validationErrors = new Dictionary<string, string>();
public void Validate()
{
bool isValid = !string.IsNullOrEmpty(_text);
bool contains = _validationErrors.ContainsKey(nameof(Text));
if (!isValid && !contains)
_validationErrors.Add(nameof(Text), "Mandatory field!");
else if (isValid && contains)
_validationErrors.Remove(nameof(Text));
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(nameof(Text)));
}
public bool HasErrors => _validationErrors.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
string message;
if (_validationErrors.TryGetValue(propertyName, out message))
return new List<string> { message };
return null;
}
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
}
}
}
XAML:
<Window x:Class="WpfApp2.Window3"
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:WpfApp2"
mc:Ignorable="d"
Title="Window3" Height="300" Width="300">
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="25" Text="*" DockPanel.Dock="Right" />
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
</Window.Resources>
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Height="26" Margin="62,213,0,0" VerticalAlignment="Top" Width="121" Click="Button_Click_1"/>
<TextBox x:Name="txtEmail1" Text="{Binding Text}" Height="61" Margin="116,10,194,0" Validation.ErrorTemplate="{StaticResource validationTemplate}"/>
</Grid>
</Window>
And please refer to the following blog post for more information about how data validation in WPF works.
Data validation in WPF: https://blog.magnusmontin.net/2013/08/26/data-validation-in-wpf/
I will show you how you can validate data using IDataErrorInfo. This interface has only two members which need to be implemented:
public string Error { get; } - gets an error message indicating what is wrong with this object
public string this[string columnName] { get; } - get the error message for the property with the given name.
Once you implement the IDataErrorInfo you need to set the ValidatesOnDataErrors to true in the element that you want to validate. To simplify the code example, I implemented IDataErrorInfo in the MainViewModel as you can see below:
public class MainViewModel : BindableBase, IDataErrorInfo
{
private string _firstName;
private string _lastName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; RaisePropertyChanged(); }
}
public string LastName
{
get { return _lastName; }
set { _lastName = value; RaisePropertyChanged(); }
}
public string this[string columnName]
{
get
{
string error = string.Empty;
switch (columnName)
{
case nameof(FirstName):
if (string.IsNullOrWhiteSpace(FirstName))
error = "First name cannot be empty.";
if (FirstName?.Length > 50)
error = "The name must be less than 50 characters.";
break;
case nameof(LastName):
if (string.IsNullOrWhiteSpace(LastName))
error = "Last name cannot be empty.";
break;
}
return error;
}
}
public string Error => string.Empty;
}
and in the TextBox element, I set ValidatesOnDataErrors to true:
<TextBox Text="{Binding FirstName, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
I've spent the last days reading and trying to apply the Navigation pattern from this page: https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Now, after I got my project to work I'm really confused about how the binding works here. At first I have to clarify that I don't want a Navigation pane which is always visible like in the given example. I just want to use my MainView for navigation and each "SubView" should be able to go back to it's "parent" only.
Here's what I've got:
Project: APP
Class: App.xaml.cs
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
UI.View.Main.MainView app = new UI.View.Main.MainView();
UI.View.Main.MainViewModel viewModel = new UI.View.Main.MainViewModel(some dependencies);
app.DataContext = viewModel;
app.Show();
}
ViewModel Base Class
public abstract class BaseViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name {
get {
return _name;
}
set {
if (Name != value) {
_name = value;
OnPropertyChanged("Name");
}
}
}
private BaseViewModel _homePage;
public BaseViewModel HomePage {
get {
return _homePage;
}
set {
if (HomePage != value) {
_homePage = value;
OnPropertyChanged("HomePage");
}
}
}
public void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null) {
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainViewModel
namespace SGDB.UI.View.Main {
public class MainViewModel : BaseViewModel {
private BaseViewModel _currentPageViewModel;
public BaseViewModel CurrentPageViewModel {
get {
return _currentPageViewModel;
}
set {
if (CurrentPageViewModel != value) {
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
public List<BaseViewModel> PageViewModels { get; private set; }
public RelayCommand ChangePageCommand {
get {
return new RelayCommand(p => ChangeViewModel((BaseViewModel)p), p => p is BaseViewModel);
}
}
//Some Dependencies
public List<BaseViewModel> ViewPages { get; private set; }
public MainViewModel(some dependencies) {
HomePage = new HomeViewModel() { Name = "TEST" };
//assign dependencies
var uavm = new UserAdministration.UserAdministrationViewModel(_userUnitOfWork, _personUnitOfWork) {
Name = Resources.Language.Sys.UserAdministartionTitle
};
PageViewModels = new List<BaseViewModel>();
PageViewModels.Add(uavm);
ChangeViewModel(HomePage);
}
public void ChangeViewModel(BaseViewModel viewModel) {
CurrentPageViewModel = viewModel;
}
}
}
MainView
<Window x:Class="SGDB.UI.View.Main.MainView"
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:SGDB.UI.View.Main"
xmlns:ua="clr-namespace:SGDB.UI.View.UserAdministration"
xmlns:home="clr-namespace:SGDB.UI.View.Home"
mc:Ignorable="d"
Title="MainView" Height="400" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:Home/>
</DataTemplate>
<DataTemplate DataType="{x:Type ua:UserAdministrationViewModel}">
<ua:UserAdministration/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPageViewModel}"/>
HomeViewModel
public class HomeViewModel : BaseViewModel {
public RelayCommand TestCommand {
get {
return new RelayCommand((x) => MessageBox.Show(x.ToString()), (x) => true);
}
}
}
HomeView
<UserControl x:Class="SGDB.UI.View.Home.Home"
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:SGDB.UI.View.Home"
xmlns:controls="clr-namespace:SGDB.UI.Controls"
xmlns:resx="clr-namespace:SGDB.UI.Resources.Language"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="800">
<Grid>
<Grid.Resources>
<Style TargetType="controls:ModernButton">
<Setter Property="Margin" Value="1"/>
<Setter Property="FontFamily" Value="Bosch Office Sans"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Size" Value="155"/>
</Style>
</Grid.Resources>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#26688B" Offset="1"/>
<GradientStop Color="#11354C" Offset="0"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Bosch Office Sans"/>
</Style>
</StackPanel.Resources>
<TextBlock Text="{x:Static resx:Sys.ApplicationTitle}" FontSize="20" FontWeight="Bold" Margin="5"/>
<TextBlock Text="{x:Static resx:Sys.ApplicationSubTitle}" FontSize="12" FontWeight="Light"/>
</StackPanel>
<WrapPanel Grid.Row="1"
Grid.Column="0"
FlowDirection="LeftToRight"
HorizontalAlignment="Left"
Width="367">
<ItemsControl ItemsSource="{Binding DataContext.PageViewModels, RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:ModernButton Background="Dark"
Text="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding HomePage}"/>
// This Button is always disabled although HomePage is of Type HomeViewModel which is based on BaseViewModel.
</WrapPanel>
</Grid>
My questions are:
Why does the HomeView knot that the HomeViewModel is it's ViewModel? I do not define it anywhere in my code.
Why does the Binding on the Name Property work but binding to the HomePage Property doesn't? Both of them are defined in the BaseViewModel class.
Update 1:
RelayCommand class:
public class RelayCommand : ICommand {
public event EventHandler CanExecuteChanged;
readonly Action<object> _action;
readonly Predicate<object> _predicate;
public RelayCommand(Action<object> action, Predicate<object> predicate) {
_action = action;
_predicate = predicate;
}
public RelayCommand(Action<object> action) {
_action = action;
_predicate = ((x) => true);
}
public bool CanExecute(object parameter) {
return _predicate(parameter);
}
public void Execute(object parameter) {
_action(parameter);
}
}
Update 2:
What's the actual problem?
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding HomePage}"/>
The Content gets bound properly but the CommandParameter (HomePage) which should be of Type BaseViewModel won't get validated through the Command's CanExecute. Both the Properties, Name and HomePage are defined inside the BaseViewModel.
Update 3:
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding DataContext.HomePage, ElementName=Test}"/>
In your there is the next lines:
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:Home/>
</DataTemplate>
meaning that the visual form of HomeViewModel is Home.
Your Binding works fine, I think your problem is the command itself. I don't know what is RelayCommand but i think your bug is from there.
RelayCommand should be something like this:
public abstract class BaseViewModel : INotifyPropertyChanged
{
private ICommand _f1KeyCommand;
public ICommand F1KeyCommand
{
get
{
if (_f1KeyCommand == null)
_f1KeyCommand = new DelegateCommand(F1KeyCommandCallback, CanExecute);
return _f1KeyCommand;
}
}
/// <summary>
/// Fired if F1 is pressend and 'CanExecute' returns true
/// </summary>
private void F1KeyCommandCallback(object obj)
{
Console.WriteLine("F1KeyCommandCallback fired");
}
// ....
}
This class allows delegating the commanding logic to methods passed as parameters,and enables a View to bind commands to objects that are not part of the element tree:
public class DelegateCommand : ICommand
{
#region Data Members
private Action<object> execute;
private Predicate<object> canExecute;
private event EventHandler CanExecuteChangedInternal;
#endregion
#region Ctor
public DelegateCommand(Action<object> execute)
: this(execute, DefaultCanExecute)
{
}
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
if (canExecute == null)
{
throw new ArgumentNullException("canExecute");
}
this.execute = execute;
this.canExecute = canExecute;
}
#endregion
#region Properties
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
this.CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
this.CanExecuteChangedInternal -= value;
}
}
#endregion
#region Public Methods
public bool CanExecute(object parameter)
{
return this.canExecute != null && this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChangedInternal;
if (handler != null)
{
handler.Invoke(this, EventArgs.Empty);
}
}
public void Destroy()
{
this.canExecute = _ => false;
this.execute = _ => { return; };
}
#endregion
#region Private Methods
private static bool DefaultCanExecute(object parameter)
{
return true;
}
#endregion
}
In your view:
<controls:ModernButton Background="Dark"
Text="{Binding Name}"
Command="{Binding F1KeyCommand"
CommandParameter="{Binding}"/>
I have Observable collection with MarketPrices , This observable collection I have bind to an ItemsControl as Below .
1) Now I don't want to show all Items in Observable Collection , want to show only items that user click Add and selected Pair (GBPJPY, USDGBP..) needs to show in Items Control.
2) If user changed item in Comobobox from GBPJPY to USDGBP , then the price( DataTemplate) of GBPJPY need to update USDGBP.
How can I achieve both conditions. Please note that below code doesn't have real-time update but in project I have relatime price update as well, so observable collection updates on price changes.
Code So Far
public class PriceModel : INotifyPropertyChanged
{
private double _askPrice;
private double _offerPrice;
private string _selectedPair;
public PriceModel()
{
Pairs = new ObservableCollection<string> {"GBPUSD", "GBPEUR", "USDGBP", "GBPJPY"};
}
public double AskPrice
{
get { return _askPrice; }
set
{
_askPrice = value;
OnPropertyChanged("AskPrice");
}
}
public double OfferPrice
{
get { return _offerPrice; }
set
{
_offerPrice = value;
OnPropertyChanged("OfferPrice");
}
}
public string SelectedPair
{
get { return _selectedPair; }
set
{
_selectedPair = value;
OnPropertyChanged(SelectedPair);
}
}
public ObservableCollection<string> Pairs { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
MarketPrices = new ObservableCollection<PriceModel>
{
new PriceModel {AskPrice = 1.60345, OfferPrice = 1.60335, SelectedPair = "GBPUSD"},
new PriceModel {AskPrice = 1.71345, OfferPrice = 1.71335, SelectedPair = "GBPEUR"},
new PriceModel {AskPrice = 1.23345, OfferPrice = 1.23335, SelectedPair = "USDGBP"},
new PriceModel {AskPrice = 1.34345, OfferPrice = 1.34335, SelectedPair = "GBPJPY"}
};
}
public ObservableCollection<PriceModel> MarketPrices { get; set; }
}
XAML
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding MarketPrices}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="5" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel AllowDrop="True" ClipToBounds="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding Pairs}" SelectedItem="{Binding SelectedPair}" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<TextBlock Margin="2" Text="Ask Price" />
<TextBlock Margin="2" Text="{Binding AskPrice}" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Margin="2" Text="Offer Price" />
<TextBlock Margin="2" Text="{Binding OfferPrice}" />
</StackPanel>
</Grid>
</Grid>
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
If I understand your question correctly, you want to display a list of pairs in a ComboBox and the details for a Selected pair only but not for all pairs?
If that's the case, there are couple problems with the code.
PriceModel
You do not need a collection of all available pairs in your PriceModel class. Also, You do not need SelectedPair property in this class, may be your intention was to indicate the name of the pair, you could update your PriceModel to:
public class PriceModel : INotifyPropertyChanged
{
private double _askPrice;
private double _offerPrice;
private string _pair;
public PriceModel(string pair)
{
_pair = pair;
}
public string Pair
{
get { return _pair; }
}
public double AskPrice
{
get { return _askPrice; }
set
{
_askPrice = value;
OnPropertyChanged("AskPrice");
}
}
public double OfferPrice
{
get { return _offerPrice; }
set
{
_offerPrice = value;
OnPropertyChanged("OfferPrice");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml.cs
You have a property named MarketPrices to hold the collection of Pairs, but no property to hold the Selected pair. Add a property named SelectedPair of type PriceModel. The Updated code would like this:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private PriceModel _selectedPair;
public MainWindow()
{
InitializeComponent();
DataContext = this;
MarketPrices = new ObservableCollection<PriceModel>
{
new PriceModel("GBPUSD") {AskPrice = 1.60345, OfferPrice = 1.60335, },
new PriceModel("GBPEUR") {AskPrice = 1.71345, OfferPrice = 1.71335, },
new PriceModel("USDGBP") {AskPrice = 1.23345, OfferPrice = 1.23335, },
new PriceModel("GBPJPY") {AskPrice = 1.34345, OfferPrice = 1.34335, }
};
}
public ObservableCollection<PriceModel> MarketPrices { get; set; }
public PriceModel SelectedPair
{
get { return _selectedPair; }
set
{
_selectedPair = value;
OnPropertyChanged("SelectedPair");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
You could just use ComboBox to display the list of available pairs and update your bindings for TextBoxes to refer SelectedPair.
Update XAML would look like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding Pairs}" SelectedItem="{Binding SelectedPair}" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<TextBlock Margin="2" Text="Ask Price" />
<TextBlock Margin="2" Text="{Binding SelectedPair.AskPrice}" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Margin="2" Text="Offer Price" />
<TextBlock Margin="2" Text="{Binding SelectedPair.OfferPrice}" />
</StackPanel>
</Grid>
</Grid>
Sample Output
I have created a ListBox with custom ListBoxItem Template, had bound everything and it worked. When I was still working on my project. I runned the program and that ListBox weren't showing items anymore. Here is my code:
This is ListBox which doesn't show any item:
<ListBox
x:Name="LB_SongList"
HorizontalAlignment="Left"
Height="498"
Margin="0,30,0,0"
VerticalAlignment="Top"
Width="319"
Background="{x:Null}"
BorderBrush="{x:Null}"
Drop="LB_SongList_Drop"
PreviewMouseMove="LB_SongList_PreviewMouseMove"
PreviewMouseDoubleClick="LB_SongList_PreviewMouseDoubleClick"
SelectionChanged="LB_SongList_SelectionChanged"
AllowDrop="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Multiple"
ItemTemplate="{DynamicResource SongTemplate}"/>
Here is my ListBoxItem Template:
<DataTemplate x:Key="SongTemplate">
<Grid Width="302" Height="35">
<Label Content="{Binding SongName}" HorizontalAlignment="Stretch" Margin="4" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Padding="2,0,0,0" VerticalContentAlignment="Center"/>
<Border BorderBrush="#B26A6A6A" BorderThickness="4" HorizontalAlignment="Stretch" Height="35" VerticalAlignment="Top" Width="Auto">
<Border.Style>
<Style>
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Value="False">
<Setter
Property="Border.Visibility"
Value="Collapsed"
/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</Grid>
</DataTemplate>
And the code:
public partial class MenuHolder : UserControl
{
private List<string> List_SongList;
public List<string> L_SongList
{
get { return List_SongList; }
set
{
_File.ListToObservableCollection_Song(value, O_SongList);
List_SongList = value;
}
}
public List<string> L_PlayLists
{
get
{
Settings.Default.Save();
return Settings.Default.L_PlayLists;
}
set
{
Settings.Default.L_PlayLists = value;
Settings.Default.Save();
}
}
public ObservableCollection<Song> O_SongList = new ObservableCollection<Song>();
public ObservableCollection<string> O_PlayList = new ObservableCollection<string>();
public MenuHolder()
{
InitializeComponent();
LB_SongList.ItemsSource = O_SongList;
LB_PlayList.ItemsSource = O_PlayList;
List<string> temp = new List<string>();
temp.Add("TestSong");
L_SongList = temp;
}
}
public class Song : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _songName;
string _songLengh;
public Song(){}
public Song(String _name){ SongName = _name; }
public Song(String _name, String _lengh) { SongName = _name; SongLengh = _lengh; }
public string SongName
{
get { return _songName; }
set { _songName = value; RaisePropertyChanged("SongName"); }
}
public string SongLengh
{
get { return _songLengh; }
set { _songLengh = value; RaisePropertyChanged("SongLengh"); }
}
private void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And the ListToObservableCollection_Song
public void ListToObservableCollection_Song(List<string> _list, ObservableCollection<Music_Player.Menus.MainMenuObjects.Song> _collection)
{
_collection.Clear();
foreach (string _path in _list)
{
_collection.Add(new Menus.MainMenuObjects.Song(GetSongNameFromPath(_path)));
}
}
The worst thing is that it was working before and in the old version of my program it is still working. Still thinking why it is doing that.