WPF MulitBinding of Slider and TextBox not updating property - wpf

I'm trying to use MultiBinding to bind a slider to a textbox (which works) and bind the TextBox to a property (which doesn't work). The TextBox/Property binding works fine with single binding, but when I introduce MultiBinding, it breaks.
Here's my XAML
<Slider
Name="SliderExportQuality"
Value="100"
Minimum="0"
Maximum="100"
HorizontalAlignment="Left"
Margin="10,5,0,0"
VerticalAlignment="Top"
Width="239"/>
<TextBox>
<TextBox.Text>
<MultiBinding StringFormat="N2">
<Binding ElementName="SliderExportQuality" Path="Value"/>
<Binding Path="ExportQuality" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
Here's the dialog box. The TextBox is trimmed with red after I try entering a value directly into it, which is telling me something's wrong?
I read up a bit on MultiBinding and think I may be going awry with my Converter but am clueless with what it should be.

As others have mentioned in the comments, unless I am misunderstanding the question, you should not need a multibinding to accomplish what you are trying to do. To get the slider value to display in the text box ( and the other way around ) you just need to bind the value to a common property in your view model.
For example, given the following xaml:
<Grid Margin="20" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Slider Grid.Column="0" Minimum="0" Maximum="100" Value="{Binding FileSize}"/>
<TextBox Grid.Column="1" Text="{Binding FileSize}"/>
</Grid>
You have a slider which binds its value to the FileSize property in your view model.
The associated ViewModel:
class MainWindowViewModel : INotifyPropertyChanged
{
public int FileSize
{
get
{
return mFileSize;
}
set
{
if(mFileSize != value)
{
mFileSize = value;
OnPropertyChanged(nameof(FileSize));
}
}
} private int mFileSize = 50;
private void OnPropertyChanged(String propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
This allows the value to be changed by either the slider, or by typing directly into the text box. There still needs to be error checking on the text box as a user could type in anything... But this shows the basic concept.
This produces the following UI.
I hope that addresses the question you were asking.

When you use that Binding it will try to set the "75" (notice it's string) and value of ExportQuality (don't know the type of this though) on the Slider.Value property, which is double, it has red border because the type is wrong (i.e. TextBox is trying to use incorrect type for the Binding, this also happens if you Bind TextBox to an int property and you type in "a"), should you use a converter this is the case when it will ConvertBack();
FYI MultiValueConverter would be used in this case.
What I think you wanted to use here was PriorityBinding. Which would be used like this:
<TextBox>
<TextBox.Text>
<PriorityBinding>
<Binding ElementName="SliderExportQuality" Path="Value"/>
<Binding Path="ExportQuality" UpdateSourceTrigger="PropertyChanged"/>
</PriorityBinding>
</TextBox.Text>
</TextBox>
This way if the first Binding fails the second will kick in.

Related

User Control is not updating in window (WPF)

There is a UserControl which contains the bindings like below.
<TextBox Margin="5" Padding="0" IsReadOnly="True" Background="Transparent" BorderThickness="0" TextWrapping="Wrap" IsTabStop="False" FontSize="{DynamicResource TitleFontSize}" Text="{Binding ErrorTitle, Mode=OneWay}" />
it is bound by stack panel with name of GenericErrorControl and binding is as
<Visibility="{Binding IsShown, Mode=OneWay, Converter={StaticResource BoolToVis}, FallbackValue=Collapsed}">
Above control is added to one of view as below.
<views:GenericErrorControl Grid.Row="8" DataContext="{Binding GenericErrorControl, Mode=OneWay}" VerticalAlignment="Top/>
The problem is that user control is not appearing in the window after. In my viewmodel creating object i'm setting the value of IsShown but its not appearing. Kindly help and let me know if any other details needed.
It means that the binding is failing ie the fallback value is used from above code.
Things to do :
Ensure that your ViewModel inherits from the base class BindableBase ie it somehow implements the INotifyPropertyChanged interface and on property change, the PropertyChanged event is fired.
ie You have something similar to this in your view model.
private bool _IsShown;
public bool IsShown
{
get { return _IsShown; }
set { SetProperty(ref _IsShown, value); }
}
Double check your converter or post code here.

WPF: get my object index after cell value changed

So i have this Slider inside my ListView:
<DataTemplate x:Key="MyDataTemplate2">
<Grid Margin="-6" >
<Slider Name="sliderColumn" HorizontalAlignment="Left" VerticalAlignment="Center" TickPlacement="None"
Minimum="0" Maximum="50" Value="1" Style="{StaticResource SliderStyle}" Width="80"
TickFrequency="1" IsSnapToTickEnabled="True"/>
<TextBlock Text="{Binding Path=Value, ElementName=sliderColumn, StringFormat={}x{0}}" FontSize="11" Foreground="White"
VerticalAlignment="Center" HorizontalAlignment="Right"/>
</Grid>
</DataTemplate>
<GridViewColumn x:Name="SpeedCell" Width="100" Header="Speed" CellTemplate="{StaticResource MyDataTemplate2}" />
And after this Slider value changed i can get the new value:
private void sliderColumn_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
double val = e.NewValue;
}
But i am still missing the most important information i need: the index number of the object the it's Slider value changed.
The reason i need the index is because my ListView is full with my objects, this objects are inside RadObservableCollection and after this Slider value changed i want to set the new value base on the index number.
Any suggestions how to find this index ?
What you want to do it to write your value back in what ever object you store your data in. In your case you want to write it into a property of your list entries DataContext. It is really simple to do so. You just bind the Slider.Value to the property of your backing class.
Now I assume the class you bound your list entries to (so what ever class is on the ObservableCollection has a property SliderValue that stores a int. Like so:
public int SliderValue { get; set; }
So you bind your slider to the specific property in your backing class.
<Slider Value="{Binding Path=SliderValue}" />
I removed all formatting code from the slider tag to focus on the relevant bits you need to add.
EDIT: So regarding your last comment SliderValue would be Num.

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

wpf trouble using dependency properties in a UserControl

I made a UserControl that is meant to be updated once every few seconds with data from a serial port. This UserControl should be very simple, consisting of a Label for a field name, and another Label containing the field value. I say that it should be simple, but it doesn't work. It does not update at all, and doesn't even display the field name.
Below is the code:
public partial class LabeledField : UserControl {
public LabeledField() {
InitializeComponent();
}
public string fieldName {
get { return fieldNameLabel.Content.ToString(); }
set { fieldNameLabel.Content = value; }
}
public string fieldValue {
get { return (string)GetValue(fieldValueProperty); }
set { SetValue(fieldValueProperty, value); }
}
public static readonly DependencyProperty fieldValueProperty =
DependencyProperty.Register(
"fieldValue",
typeof(string),
typeof(LabeledField),
new FrameworkPropertyMetadata(
"No Data"
)
)
;
}
Here is the XAML:
<UserControl x:Class="DAS1.LabeledField" Name="LF"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal">
<Label Width="100" Height="30" Background="Gray" Name="fieldNameLabel" />
<Label Width="100" Height="30" Background="Silver" Name="fieldValueLabel" Content="{Binding fieldValue}" />
</StackPanel>
And here is the XAML for the Window which references the UserControl. First the header:
<Window x:Class="DAS1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:me="clr-namespace:DAS1"
Title="Window1" Height="580" Width="780">
Then the UserControl itself:
<me:LabeledField fieldName="Test" Width="200" Height="30" fieldValue="{Binding businessObjectField}"/>
If I knew of a more specific question to ask, I would--but can anyone tell me why this doesn't work?
Turns out that in the XAML for the user control, the binding was incorrectly specified.
Originally it was:
<Label Width="100" Height="30" Name="fieldValueLabel" Content="{Binding fieldValue}" />
But I had not specified the element to which fieldValue belongs. It should be (assuming my user control is named "LF":
<Label Width="100" Height="30" Name="fieldValueLabel" Content="{Binding ElementName=LF, Path=fieldValue}" />
If you want to bind to properties of the control, you should specify so in the binding. Bindings are evaluated relative to DataContext if their source isn't explicitly specified, so your binding doesn't bind to your control, but to the inherited context (which is likely missing the property you're binding to). What you need is:
<Label Width="100" Height="30" Name="fieldValueLabel"
Content="{Binding Path=fieldValue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DAS1.LabeledField}}}" />
You really don't need a dependency property on your user control, in fact you should strive to keep user controls without anything special in the code-behind, custom Controls should be used for that.
You should define your UserControl like this (without any code behind):
<UserControl x:Class="DAS1.LabeledField"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal">
<Label Width="100" Height="30" Name="fieldNameLabel" Content="{Binding fieldName}" />
<Label Width="100" Height="30" Name="fieldValueLabel" Content="{Binding field}" />
</StackPanel>
Then, make sure your business object implements INotifyPropertyChanged, because you cannot update from your business object efficiently without modifying it at least this much. The fieldName is just an example how you could bind the display name on the label automatically to a property on your business object.
Then, simply make sure the DataContext of your UserControl is your business object.
How will this work? The Label.Content property is a DependencyProperty and will support binding itself. Your business object implements INotifyPropertyChanged and thus supports updates to binding - without it, the binding system does not get notified when your field's value changes, regardless if you bound it to a DependencyProperty on one end.
And if you want to reuse this user control elsewhere, simply place a desired instance of your business object to the DataContext of the desired LabeledField control. The binding will hook up itself from the DataContext.

WPF binding to Listbox selectedItem

Can anyone help with the following - been playing about with this but can't for the life of me get it to work.
I've got a view model which contains the following properties;
public ObservableCollection<Rule> Rules { get; set; }
public Rule SelectedRule { get; set; }
In my XAML I've got;
<ListBox x:Name="lbRules" ItemsSource="{Binding Path=Rules}"
SelectedItem="{Binding Path=SelectedRule, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox x:Name="ruleName">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Now the ItemsSource works fine and I get a list of Rule objects with their names displayed in lbRules.
Trouble I am having is binding the SelectedRule property to lbRules' SelectedItem. I tried binding a textblock's text property to SelectedRule but it is always null.
<TextBlock Text="{Binding Path=SelectedRule.Name}" />
The error I'm seeing in the output window is:
BindingExpression path error: 'SelectedRule' property not found.
Can anyone help me with this binding - I can't see why it shouldn't find the SelectedRule property.
I then tried changing the textblock's text property as bellow, which works. Trouble is I want to use the SelectedRule in my ViewModel.
<TextBlock Text="{Binding ElementName=lbRules, Path=SelectedItem.Name}" />
Thanks very much for your help.
First off, you need to implement INotifyPropertyChanged interface in your view model and raise the PropertyChanged event in the setter of the Rule property. Otherwise no control that binds to the SelectedRule property will "know" when it has been changed.
Then, your XAML
<TextBlock Text="{Binding Path=SelectedRule.Name}" />
is perfectly valid if this TextBlock is outside the ListBox's ItemTemplate and has the same DataContext as the ListBox.
Inside the DataTemplate you're working in the context of a Rule, that's why you cannot bind to SelectedRule.Name -- there is no such property on a Rule.
To bind to the original data context (which is your ViewModel) you can write:
<TextBlock Text="{Binding ElementName=lbRules, Path=DataContext.SelectedRule.Name}" />
UPDATE: regarding the SelectedItem property binding, it looks perfectly valid, I tried the same on my machine and it works fine. Here is my full test app:
XAML:
<Window x:Class="TestWpfApplication.ListBoxSelectedItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBoxSelectedItem" Height="300" Width="300"
xmlns:app="clr-namespace:TestWpfApplication">
<Window.DataContext>
<app:ListBoxSelectedItemViewModel/>
</Window.DataContext>
<ListBox ItemsSource="{Binding Path=Rules}" SelectedItem="{Binding Path=SelectedRule, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
Code behind:
namespace TestWpfApplication
{
/// <summary>
/// Interaction logic for ListBoxSelectedItem.xaml
/// </summary>
public partial class ListBoxSelectedItem : Window
{
public ListBoxSelectedItem()
{
InitializeComponent();
}
}
public class Rule
{
public string Name { get; set; }
}
public class ListBoxSelectedItemViewModel
{
public ListBoxSelectedItemViewModel()
{
Rules = new ObservableCollection<Rule>()
{
new Rule() { Name = "Rule 1"},
new Rule() { Name = "Rule 2"},
new Rule() { Name = "Rule 3"},
};
}
public ObservableCollection<Rule> Rules { get; private set; }
private Rule selectedRule;
public Rule SelectedRule
{
get { return selectedRule; }
set
{
selectedRule = value;
}
}
}
}
Yocoder is right,
Inside the DataTemplate, your DataContext is set to the Rule its currently handling..
To access the parents DataContext, you can also consider using a RelativeSource in your binding:
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ____Your Parent control here___ }}, Path=DataContext.SelectedRule.Name}" />
More info on RelativeSource can be found here:
http://msdn.microsoft.com/en-us/library/system.windows.data.relativesource.aspx
For me, I usually use DataContext together in order to bind two-depth property such as this question.
<TextBlock DataContext="{Binding SelectedRule}" Text="{Binding Name}" />
Or, I prefer to use ElementName because it achieves bindings only with view controls.
<TextBlock DataContext="{Binding ElementName=lbRules, Path=SelectedItem}" Text="{Binding Name}" />
There is a shorter version to bind to a selected item's property:
<TextBlock Text="{Binding Rules/Name}" />
since you set your itemsource to your collection, your textbox is tied to each individual item in that collection. the selected item property is useful in this scenario if you were trying to do a master-detail form, having 2 listboxes. you would bind the second listbox's itemsource to the child collection of rules. in otherwords the selected item alerts outside controls that your source has changed, internal controls(those inside your datatemplate already are aware of the change.
and to answer your question yes in most circumstances setting the itemsource is the same as setting the datacontext of the control.

Resources