WPF binding on Slider does not update on Maximum change - wpf

combobox1.SelectedItem is bound to SelectedItemProperty.
The when SelectedItemProperty is set, MaxValueProperty is calculated
MaxValueProperty is bound to slider1.Maximum
slider1.Value is bound to SliderValueProperty
These all work fine, except when combobox1.SelectedItem changes, and MaxValueProperty is calculated, and comes out less than SliderValueProperty.
In the View:
slider1.Maximum is updated, because MaxValueProperty was changed,
slider1.Value is set to slider1.Maximum as default behavior of a slider when the max changes to less than the value.
However, when this happens, SliderValueProperty does not get updated to the new slider1.Value
<Slider Name="slider1" Maximum="{Binding Path=MaxValueProperty, Mode=TwoWay}" Value="{Binding Path=SliderValueProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
I know that slider1.Value is getting changed, because I have a label bound to it, and the label changes
<Label Content="{Binding ElementName=slider, Path=Value, Converter={StaticResource NumberToStringConverter}}" />
How can I ensure the binding is updated?

Rather than relying on the default behavior of the slider and a TwoWay binding, raise a PropertyChanged event after you compute the new maximum in your SelectedItemProperty setter, first on "SliderValueProperty" using the new maximum, and then second on "MaxValueProperty". Do your computations on your private backing fields, not on the public properties.
This will cause the system to adjust first the value of the slider, and then the maxvalue, and it should move the slider position for you.
Set the binding mode on slider1's Maximum to OneWay, as well; I think that will do the trick.
As follows with a viewmodel like this:
Class viewmodel
Implements INotifyPropertyChanged
Private _selectedItemProperty As ComboBoxItem
Public Property SelectedItemProperty As ComboBoxItem
Get
Return _selectedItemProperty
End Get
Set(value As ComboBoxItem)
MaxValueProperty = value.Content
If _sliderValueProperty > _maxValueProperty Then _sliderValueProperty = _maxValueProperty
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("MaxValueProperty"))
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("SliderValueProperty"))
End Set
End Property
Private _maxValueProperty, _sliderValueProperty as Single
Public Property MaxValueProperty As Single ' with setter that will raise PropertyChanged and use the backing field
'...
Public Property SliderValueProperty As Single ' with a setter that will raise PropertyChanged and use the backing field
'...
End Class
And the XAML looks a little like this:
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="40,38,0,0" Name="ComboBox1" VerticalAlignment="Top" Width="120" SelectedItem="{Binding SelectedItemProperty}">
<ComboBoxItem Content="50" />
<ComboBoxItem Content="30" />
<ComboBoxItem Content="10" />
</ComboBox>
<Slider Name="slider1" Height="23" HorizontalAlignment="Left" Margin="87,135,0,0" VerticalAlignment="Top" Width="100" Value="{Binding SliderValueProperty}" Minimum="1" Maximum="{Binding MaxValueProperty}" />
<Label Content="{Binding ElementName=slider1, Path=Value}" Margin="40,174,293,96" />
</Grid>

Related

WPF - Combobox does not show selecteditem

Im new to WPF and VB and im having some trouble figuring out why the thing i select in a ComboBox dropdown does not show in the ComboBox after selection.
I have my ComboBox populated through bindings and DataContext. This is my Settings.xaml file
<Window.Resources>
<DataTemplate x:Key="TabList">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Header}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
...
<ComboBox x:Name="tabs"
ItemsSource="{Binding tabList}"
ItemTemplate="{StaticResource ResourceKey=TabList}"
Height="32" />
The views codebehind file (Settings.xaml.vb) then loads the ComboBoxes content in the class' constructor, and the data does show in the ComboBox
Public Class Settings
Private loader As SettingsLoader
Sub New()
InitializeComponent()
Dim sh As New SettingsHandler(True)
loader = New SettingsLoader
loader.tabList = sh.Current.Tabs
DataContext = loader
End Sub
End Class
The SettingsLoader class looks like so. TRTab is my own class that simply inherits from TabItem and only adds a few extra properties, nothing fancy
Public Class SettingsLoader
Private _tabs As List(Of TRTab)
Public Property tabList() As List(Of TRTab)
Get
Return _tabs
End Get
Set(value As List(Of TRTab))
_tabs = value
End Set
End Property
End Class
Do i need to add a property to my SettingsLoader that holds the selected item for the ComboBox to show or what am i missing ?
EDIT: Just to clarify what im trying to achieve: I have a TabControl with a number of tabs. Those tabs' Headers needs to be also shown in a ComboBox for selection
Because TabItem is a ContentControl the ComboBox will display its Content when the item is selected. You could confirm this yourself using the following XAML markup:
<Window.Resources>
<DataTemplate x:Key="TabList">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Header}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ComboBox x:Name="tabs" Height="32"
ItemTemplate="{StaticResource TabList}">
<TabItem Content="Content" Header="Header" />
</ComboBox>
</StackPanel>
When you open the dropdown, you will see "Header" but when you select the item and close the dropdown you will see "Content".
It generally doesn't make a whole much sense to set the ItemsSource property of a ComboBox to an IEnumerable of ContentControls. You could bind the ItemsSource to an IEnumerable(Of String) instead. Just add another property to your SettingsLoader class:
Public Class SettingsLoader
Private _tabs As List(Of TRTab)
Public Property tabList() As List(Of TRTab)
Get
Return _tabs
End Get
Set(value As List(Of TRTab))
_tabs = value
End Set
End Property
Public ReadOnly Property tabHeaders() As IEnumerable(Of String)
Get
If _tabs Is Nothing Then
Return Nothing
End If
Return _tabs.Select(Function(x) x.Header.ToString())
End Get
End Property
End Class
<Window.Resources>
<DataTemplate x:Key="TabList">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ComboBox x:Name="tabs"
ItemsSource="{Binding tabHeaders}"
ItemTemplate="{StaticResource ResourceKey=TabList}"
Height="32" />
</StackPanel>
The other option is to set the Content properties of your TRTab objects to the same values ass their Header properties.

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.

What is wrong with this WPF binding?

The instance of the class is a private member of the view code exposed as a public property named "ViewModel".
You are setting the DataContext of the Grid to a string equal to "ViewModel". You need to make sure the DataContext property is correctly set to actual ViewModel object instance, either with a binding or via code behind.
For more information, see my answer to the question What is DataContext for?
I'm agree with the Rachel's answer. An easy way to set the DataContext of your Grid could be this:
<Window.Resources>
<YourNamespace:ViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<TextBox Text="{Binding Path=TestName}" Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="TextBox1" VerticalAlignment="Top" Width="479" />
</Grid>
This way you don't need to touch the code behind of your Window/UserControl.
If you don't want to change the code in your view and and want to keep your ViewModel property, then you could also do this:
Public Class View Inherits Window
Private m_ViewModel As ViewModel
Public Property ViewModel() As ViewModel
Get
Return m_ViewModel
End Get
Set
m_ViewModel = Value
End Set
End Property
Public Sub New()
InitializeComponent()
ViewModel = New ViewModel()
DataContext = ViewModel
End Sub
End Class
So you don't need to set the DataContext in your view, just do this:
<Grid>
<TextBox Text="{Binding Path=TestName}" Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="TextBox1" VerticalAlignment="Top" Width="479" />
</Grid>

WPF Binding: How do I reference element outside a user control?

My main window has following portion of XAML
<StackPanel>
<TextBox x:Name="textbox1" />
<my:UserControl1 />
</StackPanel>
my:UserControl1 goes like this
<TextBox x:Name="userControlTextBox" />
I would like to bind the above textbox's Text property to Text property of the textbox outside.
I have tried
<TextBox x:Name="userControlTextBox" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=textbox1.Text}" />
You can't bind like you shown in the question. There are two ways to achieve that:
First, bind Text of textBox1 to some property in ViewModel. And bind Text of userControlTextBox to same bound property.
<TextBox x:Name="textbox1" Text="{Binding SomeProperty}"/>
and in UserControl
<TextBox x:Name="userControlTextBox" Text="{Binding SomeProperty}"/>
Make sure your property is notifiable (INPC should be implemented by containing class) so that any changes in it gets propagated to GUI.
Second would be to create custom DP on your userControl of type string. Bind that DP with Text value of textbox1. And bind Text of userControlTextBox to that DP.
<StackPanel>
<TextBox x:Name="textbox1" />
<my:UserControl1 TextValue="{Binding Text, ElementName=textBox1}"/>
</StackPanel>
Here, TextValue is custom DP declared in your userControl.
public string CustomValue
{
get { return (string)GetValue(CustomValueProperty); }
set { SetValue(CustomValueProperty, value); }
}
public static readonly DependencyProperty CustomValueProperty =
DependencyProperty.Register("CustomValue", typeof(string),
typeof(UserControl1));
Now, bind textBox in UserControl like this:
<TextBox x:Name="userControlTextBox"
Text="{Binding TextValue, RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl}}"/>

WPF User Control Data Binding Not Working

I'm creating a simple User Control combining a popup with a text view, nothing crazy. When I set it up in a window at first to style it all out, it worked perfectly, but when I moved it into a User Control to actually finish it up, it won't work correctly any more.
I pass a min and max value into the control and then it automatically creates a list of numbers to pick from in that range. In the User Control, the list of numbers doesn't get bound correctly, who knows why. Maybe someone can take a look at my code.
I've read a bunch of other questions about this, but don't really know what's happening. I'm not getting any errors in my output window, so no clues there. Anyway, here's the code -
UserControl.xaml
<UserControl x:Class="UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Me"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<TextBox Name="myTextbox"
Height="30"
Margin="0"
FontSize="14"
IsReadOnly="True"
Padding="5,2"
Text="{Binding Value}" />
<Popup x:Name="myPopup"
PlacementTarget="{Binding ElementName=myTextbox}"
StaysOpen="True">
<Popup.Style>
<Style TargetType="{x:Type Popup}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myTextbox, Path=IsFocused}" Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
<StackPanel>
<ListView Name="myListView"
Height="100"
MaxHeight="300"
ItemsSource="{Binding List,
UpdateSourceTrigger=PropertyChanged}"
SelectionChanged="ListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Label Width="100"
Height="30"
Margin="0"
Content="{Binding}"
FontFamily="Segoe UI"
FontSize="14"
Padding="5,2" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Width="200" Height="30" />
</StackPanel>
</Popup>
</StackPanel>
UserControl.xaml.vb
Imports System.ComponentModel
Imports System.Linq.Expressions
Imports System.Collections.ObjectModel
Class UserControl1
' Dependency Properties
Public Shared ReadOnly ListProperty As DependencyProperty = DependencyProperty.Register("List", GetType(ObservableCollection(Of Integer)), GetType(MainWindow))
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Integer), GetType(MainWindow))
Public Shared ReadOnly MaxValueProperty As DependencyProperty = DependencyProperty.Register("MaxValue", GetType(Integer), GetType(MainWindow))
Public Shared ReadOnly MinValueProperty As DependencyProperty = DependencyProperty.Register("MinValue", GetType(Integer), GetType(MainWindow))
' Properties
Public Property List As ObservableCollection(Of Integer)
Get
Return DirectCast(GetValue(ListProperty), ObservableCollection(Of Integer))
End Get
Set(value As ObservableCollection(Of Integer))
SetValue(ListProperty, value)
End Set
End Property
Public Property Value As Integer
Get
Return DirectCast(GetValue(ValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(ValueProperty, value)
End Set
End Property
Public Property MaxValue As Integer
Get
Return DirectCast(GetValue(MaxValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(MaxValueProperty, value)
End Set
End Property
Public Property MinValue As Integer
Get
Return DirectCast(GetValue(MinValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(MinValueProperty, value)
End Set
End Property
Private Sub ListView_SelectionChanged(sender As System.Object, e As System.Windows.Controls.SelectionChangedEventArgs)
Value = List(myListView.SelectedIndex)
End Sub
Private Sub UserControl1_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
List = New ObservableCollection(Of Integer)
' Add all available numbers into the list
For iCounter As Integer = MinValue To MaxValue
List.Add(iCounter)
Next
' Set the selected index on the list for the value
myListView.SelectedIndex = Value - MinValue
End Sub
End Class
Just for reference, when I tested this out, I set the Min and Max values myself in both the window and usercontrol setup.
I think that you have made the same mistake that I used to when I first started learning WPF. I can't guarantee that this is the cause of your problem, but as it's the only one that I can see, I'll address it. Unfortunately, there are so many poor tutorials and quick examples that show the connecting of a UserControl.DataContext to itself:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Or:
DataContext = this;
Now this is perfectly acceptable if you don't want to Bind to the UserControl externally because it's a quick and easy way to connect with properties defined in the code behind. However, when you want to Bind to the properties from outside the control, you'll find problems. In these instances (if not on all occasions), you should use a RelativeSource Binding to Bind to your code behind properties:
<TextBox Name="myTextbox" Height="30" Margin="0" FontSize="14" IsReadOnly="True"
Padding="5,2" Text="{Binding Value, RelativeSource={RelativeSource AncestorType={
x:Type UserControl1}}}" />
In this Binding, UserControl1 is the name of the UserControl that declared the properties. This should be done on all of the internal Bindings. This way, the DataContext is not set to the UserControl, but the Bindings still find the properties.
You can find out more about RelativeSource from the RelativeSource MarkupExtension page on MSDN.
As I cant provide a comment to Sheridan good answer I have to provide a new answer, sorry for this.
While I love this solution
DataContext="{Binding RelativeSource={RelativeSource Self}}"
it fails (as Sheridan pointed out already) fast.
What you can do is just set the DataContext of the content of your User Control
<UserControl x:Class="Example.View.Controls.MyUserControl"
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:controls="clr-namespace:Example.View.Controls"
mc:Ignorable="d">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:MyUserControl}}}">
</Grid>
In this way all following Bindings have less Boilerplate code as you can just bind directly to your DP from the code-behind like:
<Label Content="{Binding MyLabel}"/>
As for Windows Apps (Windows 8 and Windows 10 UWP) the way to go is to give your control a name and reference it within your XAML file using Path and ElementName:
<UserControl
x:Class="MyControl"
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"
x:Name="Control"
mc:Ignorable="d" >
<Grid Height="240" VerticalAlignment="Top">
<Rectangle Fill="{Binding ElementName=Control, Path=Background}" />
</Grid>
</UserControl>
``

Resources