Show the validation error template on a different control in WPF - wpf

I have a UserControl that contains other controls and a TextBox. It has a Value property that is bound to the TextBox text and has ValidatesOnDataErrors set to True.
When a validation error occurs in the Value property binding, the error template (standard red border) is shown around the entire UserControl.
Is there a way to show it around the TextBox only?
I'd like to be able to use any error template so simply putting border around textbox and binding its color or something to Validation.HasError is not an option.
Here's my code:
<DataTemplate x:Key="TextFieldDataTemplate">
<c:TextField DisplayName="{Binding Name}" Value="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
</DataTemplate>
<controls:FieldBase x:Name="root">
<DockPanel DataContext="{Binding ElementName=root}">
<TextBlock Text="{Binding DisplayName}"/>
<TextBox x:Name="txtBox"
Text="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True}"
IsReadOnly="{Binding IsReadOnly}"/>
</DockPanel>
UserControl (FieldBase) is than bound to ModelView which performs validation.

to accomplish this task I've used this solution. It uses converter, that "hides" border by converting (Validation.Errors).CurrentItem to Thickness.
<Grid>
<Grid.Resources>
<data:ValidationBorderConverter
x:Key="ValidationBorderConverter" />
</Grid.Resources>
<Border
BorderBrush="#ff0000"
BorderThickness="{Binding
ElementName=myControl,
Path=(Validation.Errors).CurrentItem,
onverter={StaticResource ValidationBorderConverter}}">
<TextBox
ToolTip="{Binding
ElementName=myControl,
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Border>
</Grid>
ValidationBorderConverter class is pretty simple:
[ValueConversion(typeof(object), typeof(ValidationError))]
public sealed class ValidationBorderConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return (value == null) ? new Thickness(0) : new Thickness(1);
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Related

Bind DataContext Index to Slider Control Value

Is it possible to use the current value of a WPF Slider control (Slider.Value) as an input to a Indexing Binding on another control?
Use case: A collection of items is set as the DataContext for a control, and the slider is used to select which item from a collection is displayed.
<Slider x:Name="selector" Minimum="1" Maximum="{Binding Count}"/>
<!-- How to grab the value of selector and use as indexer?? -->
<StackPanel DataContext="{Binding [??????]}">
<TextBlock Text="{Binding name}"/>
<TextBlock Text="{Binding job}" />
</StackPanel>
Is it possible to use the current value of a WPF Slider control (Slider.Value) as an input to a Indexing Binding on another control?
No, not directly. ?????? in {Binding [??????]} has to be a compile-time constant.
You could bind to both the DataContext and the Value property of the Slider and use a converter to perform the lookup though:
public class MultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var yourDataContext = values[0] as IDictionary<double, object>; //cast to whatever the type of your DataContext is
double value = (double)values[1];
return yourDataContext[value];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
XAML:
<StackPanel>
<StackPanel.DataContext>
<MultiBinding>
<MultiBinding.Converter>
<local:MultiConverter />
</MultiBinding.Converter>
<Binding Path="." />
<Binding Path="Value" ElementName="selector" />
</MultiBinding>
</StackPanel.DataContext>
<TextBlock Text="{Binding name}"/>
<TextBlock Text="{Binding job}" />
</StackPanel>
Declare two dependency properties on your data context: SliderIndex and SelectedItem (you can name them whatever you want, but those are the names I'll use for my answer).
Bind Slider.Value to SliderIndex. Then use a PropertyChangedCallback to update the SelectedItem property based on the new value of SliderIndex. Finally, bind StackPanel.DataContext to SelectedItem.
This is the best way I know of to do this. There is no easy way to bind the two directly since you can't use a variable as an index for collection binding. The other option is to use an IValueConverter or IMultiValueConverter, but the above is cleaner in my opinion.

Placing a PopUp to the top of the mouse-cursor position

I am working on an interactive chart, where I am displaying a popup with more information, when the user clicks on a data-point.This works fine so far and this is the popup definition:
<Popup IsOpen="{Binding PopupViewModel.IsOpen}"
Placement="Mouse"
HorizontalOffset="-150">
<Popup.Resources>
<DataTemplate DataType="{x:Type ViewModels:DataPointPopUpContentViewModel}">
<Views:DataPointPopUpContentView/>
</DataTemplate>
</Popup.Resources>
<Border BorderThickness="1" BorderBrush="Black" Background="White">
<ContentPresenter Content="{Binding PopupViewModel}" />
</Border>
</Popup>
The default placement of the popup, when using Placement="Mouse" is at the bottom right of the mouse-cursor. However I want the popup to be placed just at the top edge the mouse-cursor. As you can see I have achieved the horizontal centering by setting HorizontalOffset="-150", which is have of the fixed popup-width (Width=300). For the vertical placement I have the problem, that the popup-height is not fixed, since I am displaying an image of variable size and aspect-ratio inside it. I have therefore tried to set VerticalOffset to the ActualHeight of the pupup by adding VerticalOffset="{Binding ActualHeight}". This does not work unfortunately. Any ideas on what I am doing wrong and how to achieve my goal?
First of all you need a converter:
public class MultiplyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double && parameter is double)
{
return ((double)value) * ((double)parameter);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then try to bind the VerticalOffset property to the ActualHeight of the Popup's child:
<Window.Resources>
<local:MultiplyConverter x:Key="MultiplyConverter" />
<sys:Double x:Key="Factor">-.5</sys:Double>
</Window.Resources>
<Popup IsOpen="{Binding PopupViewModel.IsOpen}"
Placement="Mouse"
HorizontalOffset="-150">
<Popup.VerticalOffset>
<Binding Path="Child.ActualHeight" RelativeSource="{RelativeSource Self}"
Converter="{StaticResource MultiplyConverter}" ConverterParameter="{StaticResource Factor}" />
</Popup.VerticalOffset>
<!-- popup content -->
</Popup>
I hope it can help you.

WPF binding to TextBox MinLines MaxLines

I have an wpf Expander with templated header. In this template, i have an TextBox, which use Binding with Converter to set MaxLines and MinLines, which depends on Expander.IsExpanded.
The idea is to let user see first line of text and show more when Expander is expanded (alternate solution would be to make that TextBox.Visiblitity = Collapsed when expanded and another TextBox.Visibility = Visible, but user will lost their cursor position, marked text and i dont know what else)
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="MyTemplate">
<Grid>
<Grid.Resources>
<Converters:ExpandedToLineRowsConverter ExpandedLines="5"
CollapsedLines="1"
x:Key="ExpandedToLines"/>
</Grid.Resources>
<TextBox MaxLines="{Binding IsExpanded,
Converter={StaticResource ExpandedToLines},
RelativeSource={RelativeSource FindAncestor,
AncestorLevel=1,
AncestorType={x:Type Expander}},
UpdateSourceTrigger=PropertyChanged}"
MinLines="{Binding IsExpanded,
Converter={StaticResource ExpandedToLines},
RelativeSource={RelativeSource FindAncestor,
AncestorLevel=1,
AncestorType={x:Type Expander}},
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<Expander Header="{Binding}"
HeaderTemplate="{StaticResource MyTemplate}">
<!-- other wpf controls under expander, they do not affect the problem -->
</Expander>
</UserControl>
ExpandedToLineRowsConverter is very simple:
public class ExpandedToLineRowsConverter : IValueConverter
{
public int ExpandedLines { get; set; }
public int CollapsedLines { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? ExpandedLines : CollapsedLines;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return (int)value != CollapsedLines;
}
}
Problem is, it is working only when expanding, not when collapsing (textbox stay in multiline mode even when Expander.IsExpanded = false).
When i set breakpoint in converter, it is returning corect number of lines, but it looks like the TextBox just ignore them.
I have no idea what to do...
Edit: sample VS2012 project with problem

How to reference another control in a TreeViewItem through a binding converter?

I would like to bind the Visibility of a TextBox based on SelectedItem of a ComboBoxin same TreeViewItemContainer. I think I can use a Converter for the Binding but I don't know how to send the ComboBox item as a parameter of the TextBox Binding. Can this be done?
<TreeView>
<TreeView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox Margin="2,0" Name="SkillSelectCB" ItemsSource="{Binding PotentialChildren}" />
<TextBox Margin="2,0" Width="50" Visibility="{Binding ??}" />
<Button Margin="2,0" Content="Add" />
</StackPanel>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
This is actually in a HierarchicalDataTemplate, the example above is very minimal. The "Add" Button will add new children to the ViewModel for the TreeView based on what's selected in the ComboBox. And the visibility is the TextBox will change depending on some property of the ComboBox's SelectedItem.
So the Xaml for the TextBox:
<TextBox Margin="2,0"Width="50" Visibility="{Binding SelectedItem, ElementName=SkillSelectCB, Converter={StaticResource SkillToVisibilityConverter}}" />
And the Converter:
public class SkillToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var s = (Skill)value;
return (s == null || !s.Specialized) ? "Hidden" : "Visible";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Change DataTemplate to use depending on condition

I have 3 user controls
Control 1
Control 2
Control 3
I have a stack panel that contains an ItemsControl
<UserControl.Resources>
<DataTemplate x:Key="Template1">
<my:UserControl1 Height="117"/>
</DataTemplate>
<DataTemplate x:Key="Template2">
<my:UserControl3 Height="117"/>
</DataTemplate>
<DataTemplate x:Key="Template3">
<my:UserControl3 Height="117"/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Name="stackPanel3" Orientation="Vertical" VerticalAlignment="Bottom" Width="Auto">
<ItemsControl ItemsSource="{Binding BlocksForMonth.Blocks}" ItemTemplate="{StaticResource Template1}">
</ItemsControl>
</StackPanel>
BlocksForMonths.Blocks is a list of view models. The Blocks class has a property called ClipType. If the clipType is 1, I want to use Template1. If its 2 I want to use Template 2. If its 3 I want to use Template 3
These templates contain different user controls
How can I do this through binding?
I have considered 1 template with the 3 controls, but I dont know how to bind the visibility?
In this XAML I am binding to a list not a single item
Paul
I would put the 3 controls in the same template and use Visibility to display the correct one. What I would do is build an IValueConverter to convert the deciding value (your case it's ClipType) and compare that to the ConverterParameter. If they are equal, return Visibility.Visible, otherwise return Visibility.Collapsed.
<UserControl.Resources>
<my:ClipTypeToVisibilityConverter x:Key="converter"/>
<DataTemplate x:Key="Template">
<StackPanel>
<my:UserControl1 Height="117" Visibility={Binding ClipType, Converter={StaticResource converter}, ConverterParameter=1} />
<my:UserControl2 Height="117" Visibility={Binding ClipType, Converter={StaticResource converter}, ConverterParameter=2} />
<my:UserControl3 Height="117" Visibility={Binding ClipType, Converter={StaticResource converter}, ConverterParameter=3} />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel Name="stackPanel3" Orientation="Vertical" VerticalAlignment="Bottom" Width="Auto">
<ItemsControl ItemsSource="{Binding BlocksForMonth.Blocks}" ItemTemplate="{StaticResource Template}">
</ItemsControl>
</StackPanel>
This example assumes the ClipType property is on each item view model in the list being displayed.
Here is a C# example converter.
public class ClipTypeToVisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var clipType = value.ToString();
if (clipType == (string)parameter))
return Visibility.Visible;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Sorry, everything was air-code. But I think you get the idea.

Resources