MultiValueConverter reading from ObservablleCollection - wpf

I am working on a wpf mvvm project. In a user control I have a datagridControl from Devexpress that is bound to data from a Observable collection.
<xcdg:DataGridControl x:Name="DataGridName" HorizontalAlignment="left" VerticalAlignment="Stretch"
AutoCreateColumns="False"
ItemsSource="{Binding ViewModel.Items}"
ItemScrollingBehavior="Immediate" SynchronizeCurrent="True" TabIndex="69" >
<xcdg:DataGridControl.Columns >
<xcdg:Column FieldName="Name" AllowSort="False" Title="Name" ShowInColumnChooser="False" />
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
The class in the Observable collection Contains a Name (string) and IsVerified (Boolean).
private ObservableCollection<myData> _items = new ObservableCollection<myData>();
public ObservableCollection<myData> Items
{
get { return _items; }
set { _items = value; }
}
public class myData
{
public string Name { get; set; }
public bool IsVerfied { get; set; }
}
I also have a textblock that I use to display an error message above the dataGrid when the Value of IsVerfied is false.
<TextBlock Name="textBlockErrrMessage" Foreground="IndianRed">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MultiValueConverter}">
<Binding Path="DataContext.IsVerified" RelativeSource="{RelativeSource AncestorType=xcdg:DataRow}" ElementName="DataGridName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
To do this I plan on having a multivalueconverter (I am also doing the same thing but for a different control so that is why I choose a MultiValueConverter) that I would like to send the IsVerfied value from the Collection and return the message. My issue is how do I set the Binding in the MultiBinding to read the IsVerfied value from the Observablecollection. This particular line is what I believe is the issue in locating the Collection value
<Binding
Path="DataContext.IsVerified"
RelativeSource="{RelativeSource AncestorType=xcdg:DataRow}"
ElementName="DataGridName" />

In your Binding, you want to use either RelativeSource or ElementName, but not both. See this post for a good clarification on the differences between the two.

Related

Binding as a Resource

Can I define a Binding as a Resource and then reuse it with different Controls properties?
Example:
Binding:
<Window.Resources>
<Binding x:Key="MyBinding" Path="MyProperty" Mode="TwoWay" />
</Window.Resources>
Reuse in XAML:
<TextBox Text="{StaticResource MyBinding}" />
After declaring Binding as above I got the error:
"The name 'InitializeComponent' does not exist in the current
context"
Is there any way to reuse the same Binding in different contexts?
Direct answer to your question is "yes, you can define a binding as a resource". The problem here is how do you then make any use of it? One possibility is to create an extension class which would pull the binding from the resources and apply it:
public class BindingResourceExtension : StaticResourceExtension
{
public BindingResourceExtension() : base() { }
public BindingResourceExtension(object resourceKey) : base(resourceKey) { }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var binding = base.ProvideValue(serviceProvider) as BindingBase;
if (binding != null)
return binding.ProvideValue(serviceProvider);
else
return null; //or throw an exception
}
}
Usage example:
<Window.Resources>
<ResourceDictionary>
<Binding x:Key="MyBinding" Path="MyProperty" Mode="TwoWay" />
</ResourceDictionary>
</Window.Resources>
(...)
<TextBox Text="{ns:BindingResource MyBinding}" />
Can this solution be used in MultiBinding?
Yes, it can:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="First: {0}, Second: {1}">
<Binding Path="SomeProperty" />
<ns:BindingResource ResourceKey="MyBinding" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
There is however one drawback to this - although everything will work in run-time, the XAML Designer will complain that BindingResourceExtension is not of proper type to be put in the MultiBinding.Bindings collection. But, thankfully, there is a quick solution - simply use StaticResourceExtension instead! So this, while being functionally equivalent in run-time, will be accepted by the designer:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="First: {0}, Second: {1}">
<Binding Path="SomeProperty" />
<StaticResource ResourceKey="MyBinding" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Here are two ways to not do exactly what you want:
1. Using a custom markup extension
Skipped all nullchecks etc. to keep it short.
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
public class BindingDefinition
{
public PropertyPath Path { get; set; }
public BindingMode Mode { get; set; }
}
[MarkupExtensionReturnType(typeof(BindingExpression))]
public class ApplyBindingDefinition : MarkupExtension
{
public BindingDefinition Definition { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var binding = new Binding
{
Path = this.Definition.Path,
Mode = this.Definition.Mode
};
return binding.ProvideValue(serviceProvider);
}
}
<Window.Resources>
<local:BindingDefinition x:Key="MyProperty"
Mode="TwoWay"
Path="MyProperty" />
</Window.Resources>
<TextBox>
<TextBox.Text>
<!-- using element style here as the parser chokes on parsing nested markupextensions -->
<local:ApplyBindingDefinition Definition="{StaticResource MyProperty}" />
</TextBox.Text>
</TextBox>
2. Making the PropertyPath a resource
May or may not be enough for your needs.
<Window.Resources>
<PropertyPath x:Key="MyPropertyPath">MyProperty</PropertyPath>
</Window.Resources>
...
<TextBox Text="{Binding Path={StaticResource MyPropertyPath}}" />

WPF validation launched on focus gained

I'm developing a IDataErrorInfo to validate the textboxes I have inside my application. I have the following code:
The .cs class to validate:
public class UserInformation : IDataErrorInfo
{
public string _name;
public string _surname;
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Surname
{
get { return _surname; }
set { _surname = value; }
}
public override string ToString()
{
return Name + " " + Surname;
}
public string this[string columnName]
{
get
{
if (columnName == null) return string.Empty;
string result = string.Empty;
if (columnName.Equals("Name"))
{
if (string.IsNullOrEmpty(_name))
result = "Name cannot be empty.";
}
return result;
}
}
public string Error { get; private set; }
}
The .xaml:
<TextBox Grid.Column="3" Grid.Row="0" Name="TextBoxName"
Style="{DynamicResource InnerTextBox}"
Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}">
<TextBox.Text>
<Binding Path="Name" Source="{StaticResource UserInformation}"
ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
And the ErrorTemplate:
<ControlTemplate x:Key="ValidationErrorTemplate">
<DockPanel >
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<Grid Width="20" Height="20">
<Ellipse Width="20" Height="20" Fill="Tomato" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Foreground="White" FontWeight="Heavy" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center"
ToolTip="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">X</TextBlock>
</Grid>
<TextBlock Foreground="Tomato" FontWeight="12" Margin="2,0,0,0" FontSize="20"
Text="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
</StackPanel>
<AdornedElementPlaceholder x:Name="ErrorAdorner" />
</DockPanel>
</ControlTemplate>
The code works fine when I'm typing. But when the TextBox is loaded, the validation occurs too. And I don't want it to happen when it gains focus, only when it looses it or I change the text (like the one published here).
How can I avoid the validation error to be considered on first TextBox load?
NOTE: Even if I set the UpdateSourceTrigger to LostFocus, it is still making the validations.
To acheive you goal you need to:
First, remove ValidatesOnDataErrors="True" on your Binding. As said in docs:
Setting this property provides an alternative to using the
DataErrorValidationRule element explicitly
And we're gonna use it explicitly. Then use DataErrorValidationRule instead of ExceptionValidationRule for correctly working with IDataErrorInfo and data errors.
And last, we need to use some properties that this rule gives us:
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False" />
</Binding.ValidationRules>
ValidatesOnTargetUpdated on false will not trigger validation when target itself changes (i.e. on load). You can also play with ValidationStep property for additional control.
Edit:
Ok, I see that you need to skip validation on load and you need to validate on lost focus even if the value was not changed. Well, validation rules does not support that, because if the value was not updated, then no changed events will be called and no validation will occur, regardless of UpdateSourceTrigger setting.
The easy way out is to emulate this functionality by adding LostFocus handler to TextBox itself:
private void ValidatedTextBox_LostFocus(object sender, RoutedEventArgs e)
{
var txt = (TextBox)sender;
txt.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
If you need this for several TextBoxes, you can move the code to some static class.
The same results can be achieved using Explicit update source trigger, wich can be a little bit more cleaner.
I dont have any example atm, because I moved to what you have. But You will need to create a class which will Inherit from ValidationRule which exist in system.windows.controls, and then override Validate method.
Then your xaml textbox would look something like this instead
<TextBox.Text>
<Binding Path="your binding here" UpdateSourceTrigger="LostFocus" >
<Binding.ValidationRules>
<validationClass:yourRule/> define this at the top of xaml page
</Binding.ValidationRules>
</Binding>
You should be able to find examples on msdn, and here about validation rules

DataGridTextColumn showing/editing time only, date bound to property of parent usercontrol

I have a DataGrid within a UserControl. I want to have a DataGridTextColumn bound to a DateTime field, showing only the time. When the user enters a time, the date portion (year, month, day) should be taken from a property (AttendDate) on the UserControl.
My first thought was to bind the user control's property to ConverterParameter:
<DataGridTextColumn Header="From"
Binding="{Binding FromDate, Converter={StaticResource TimeConverter},ConverterParameter={Binding AttendDate,ElementName=UC}}"
/>
but ConverterParameter doesn't take a binding. I then thought to do this using a MultiBinding:
<DataGridTextColumn Header="משעה" Binding="{Binding FromDate, Converter={StaticResource TimeConverter}}" />
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource TimeConverter}">
<Binding Path="FromDate" />
<Binding Path="AttendDate" ElementName="UC" />
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
However IMultiValueConverter.Convert -- which takes multiple parameters -- is only called when formatting the display. IMultiValueConverter.ConvertBack which is called on editing, only takes one parameter - the entered string.
How can I do this?
(I am not using MVVM; not something I can change.)
One idea for the solution is to have another property with only a getter that merges the info you want.
something like
property string Time {get {return this.FromDate.toshortdate().tostring() + AttendDate.hour.tostring() + attenddate.minutes.tostring()}; }
The code might be not exactly this, but then you can bind this property to show the info you want where it should be presented.
regards,
=============EDIT===========
I tried this, a very simple example...don't know if it works for you:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv ="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<conv:TimeConverter x:Key="tmeConverter"></conv:TimeConverter>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding listExample}">
<DataGrid.Columns>
<DataGridTextColumn Header="teste" >
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource tmeConverter}">
<Binding Path="FromDate" />
<Binding Path="AttendDate" />
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code behind (.cs)
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public List<Example2> listExample { get; set; }
public Example2 Test { get; set; }
public MainWindow()
{
InitializeComponent();
this.listExample = new List<Example2>();
//listExample.Add(new Example { IsChecked = false, Test1 = "teste" });
//listExample.Add(new Example { IsChecked = false, Test1 = "TTTTT!" });
this.Test = new Example2 { AttendDate = "1ui", FromDate = "ff" };
this.listExample.Add(this.Test);
DataContext = this;
}
}
}
And Example2 class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApplication1
{
public class Example2
{
public string FromDate { get; set; }
public string AttendDate { get; set; }
}
}
Converter:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace WpfApplication1
{
class TimeConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values[0].ToString() + values[1].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The final window appears 3 columns, and if I change the last two columns, the first one is automatically edited.
Is that it?
Get rid of DataGridTextColumn and use DataGridTemplateColumn with CellTemplate containing a textblock bound to your multibinding, and cell editing template containing TextBox bound to FromDate, possibly via short-date converter, depending on usability you intend to achieve.
On of possible solutions:
XAML
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label>From time</Label>
<DatePicker SelectedDate="{Binding FromTime}"/>
</StackPanel>
<DataGrid ItemsSource="{Binding AllUsers}" AutoGenerateColumns="False">
<DataGrid.Resources>
<local:TimeConverter x:Key="TimeConverter"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="local:UserViewModel">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TimeConverter}">
<Binding ElementName="root" Path="DataContext.FromTime"/>
<Binding Path="AttendTimeHour"/>
<Binding Path="AttendTimeMinute"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="local:UserViewModel">
<StackPanel Orientation="Horizontal">
<TextBlock>
<Run>Attend on </Run>
<Run Text="{Binding ElementName=root, Path=DataContext.FromTime, StringFormat=d}"/>
<Run> at </Run>
</TextBlock>
<TextBox Text="{Binding AttendTimeHour}"/><TextBlock>:</TextBlock>
<TextBox Text="{Binding AttendTimeMinute}"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
View model (UserViewModel) part:
private DateTime _attendTime;
public DateTime AttendTime
{
get { return _attendTime; }
set
{
if (value == _attendTime) return;
_attendTime = value;
OnPropertyChanged();
OnPropertyChanged("AttendTimeHour");
OnPropertyChanged("AttendTimeMinute");
}
}
public int AttendTimeHour
{
get { return attendTimeHour; }
set
{
if (value.Equals(attendTimeHour)) return;
attendTimeHour = value;
AttendTime = new DateTime(AttendTime.Year, AttendTime.Month, AttendTime.Day, AttendTimeHour, AttendTime.Minute, AttendTime.Second);
OnPropertyChanged();
}
}
private int _attendTimeMinute;
public int AttendTimeMinute
{
get { return _attendTimeMinute; }
set
{
if (value == _attendTimeMinute) return;
_attendTimeMinute = value;
AttendTime = new DateTime(AttendTime.Year, AttendTime.Month, AttendTime.Day, AttendTime.Hour, AttendTimeMinute, AttendTime.Second);
OnPropertyChanged();
}
}
And the converter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2) return null;
if (!(values[0] is DateTime && values[1] is int && values[2] is int)) return null;
var fromDate = (DateTime) values[0];
var attendTimeHour = (int) values[1];
var attendTimeMinutes = (int)values[2];
var result = new DateTime(fromDate.Year, fromDate.Month, fromDate.Day, attendTimeHour, attendTimeMinutes, 0);
return result.ToString();
}

combining two field into one Text content wpf

I want to combine Name and ID of the song in the header of Song. Would it be possible if I have:
Song
{
public string Name {get; set;}
public int ID {get; set;}
}
I want to bind them into the Header of an Expander. At the moment, for binding with only one property. it is like this:
<Expander Foreground="#FFF4E7CA" Header="{Binding Song.Name}" FontWeight="Bold">
</Expander>
But I want to be some thing like this:
Header = "{Binding Some.Name, Song.ID}"
Is it possible somehow then? and if yes, how? THanks in advance.
Something like this:
<Expander ...>
<Expander.Header>
<TextBlock>
<TextBlock Text="{Binding Song.Name}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding Sond.ID}"/>
</TextBlock>
</Expander.Header>
...
</Expander>
You could either
1) expose a new property on your view model and bind to that
Song
{
public string Name {get; set;}
public int ID {get; set;}
public string Header {get { return string.Format("{0} {1}", Name, ID); } }
}
2) use a multi-binding with a string format
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="Song.Name"/>
<Binding Path="Song.Id"/>
</MultiBinding>
</TextBlock.Text>
3) If you're using WPF4, then Run is bindable so the answer regarding using Run would then work.
Yes you can use the multibinding class

How to avoid default text in textbox?

I have a textbox, which uses multi-binding usingStringFormat...as shown below.
But it displays the default value as
{DependencyProperty.UnsetValue},{DependencyProperty.UnsetValue}
How to avoid this ?
<StackPanel Orientation="Horizontal">
<TextBlock Width="70" Text="Name:" Margin="5,2,2,2"></TextBlock>
<TextBox Width="160" DataContext="{Binding }" IsReadOnly="True" Margin="2">
<TextBox.Text>
<MultiBinding StringFormat="{}{0},{1}">
<Binding Path="LastName"/>
<Binding Path="FirstName"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
Please help me.
Something is wrong with the object you are binding to. I just created an application from scratch with a Person class that inherits from DependencyObject. I left the first and last name properties unset and I did not see DependencyProperty.UnsetValue, but rather a blank TextBox with just a comma in it.
(In general, you shouldn't use dependency properties on your business objects anyway. Stick to INotifyPropertyChanged and save yourself a ton of headaches.)
Post the code to your bound object and maybe I can spot the issue.
public class Person : DependencyObject
{
public static readonly DependencyProperty FirstNameProperty = DependencyProperty.Register("FirstName", typeof(string), typeof(Person), new FrameworkPropertyMetadata());
public string FirstName {
get { return (string)GetValue(FirstNameProperty); }
set { SetValue(FirstNameProperty, value); }
}
public static readonly DependencyProperty LastNameProperty = DependencyProperty.Register("LastName", typeof(string), typeof(Person), new FrameworkPropertyMetadata());
public string LastName {
get { return (string)GetValue(LastNameProperty); }
set { SetValue(LastNameProperty, value); }
}
}
-
<TextBox IsReadOnly="True">
<TextBox.Text>
<MultiBinding StringFormat="{}{1}, {0}">
<Binding Path="FirstName" />
<Binding Path="LastName" />
</MultiBinding>
</TextBox.Text>
</TextBox>
-
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
private void Window_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
var p = new Person();
//p.FirstName = "Josh";
//p.LastName = "Einstein";
DataContext = p;
}
}
just give the fallbackvalue="" and see
<TextBox.Text>
<MultiBinding StringFormat="{}{0},{1}">
<Binding Path="LastName" FallbackValue=""/>
<Binding Path="FirstName" FallbackValue=""/>
</MultiBinding>
</TextBox.Text>
If a binding is not successful, i.e. the path to the binding source is not found or the value converter , if any, fails, a DependencyProperty.UnsetValue is returned, then the target property is set to the FallbackValue, if you defined one of course.

Resources