WPF: TwoWay binding between 4 TextBoxes and 1 Border.Margin
I want to set the BorderThickness of a Border of a UserControl using 4 TextBoxes, but I can't get it to work.
XAML code demonstrating the problem (only this code in combination with the converter is needed):
<Window
x:Class="BorderThicknessBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:BorderThicknessBindingTest="clr-namespace:BorderThicknessBindingTest"
Height="300" Width="500">
<Window.Resources>
<BorderThicknessBindingTest:ThicknessConverter x:Key="ThicknessConverter"/>
</Window.Resources>
<Grid Margin="10">
开发者_StackOverflow <Border
x:Name="MyBorder"
BorderBrush="Black"
Background="AliceBlue"
BorderThickness="3"/>
<TextBox
HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=BorderThickness.Left, ElementName=MyBorder, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ThicknessConverter}}"/>
</Grid>
</Window>
A converter is needed to parse the string input in the TextBox:
public class ThicknessConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value; // don't need to do anything here
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
double d;
Double.TryParse((string) value, out d); // Thickness.Left doesn't take a string
return d;
}
}
The TextBox correctly displays the Left part of the Thickness, but editing the TextBox does not result in a change in the way the left side of the Border is rendered. Oddly, the value that I set in the TextBox for Thickness.Left persists, so it seems that the value does get set, but the rendering isn't updated. In the example code, changing the value in the TextBox, then resizing the Window, shows that the border on the left does take up additional space, but this space is blank.
Does anyone know how to go about and fixing this?
It's not dynamically updating the element on the screen because nothing has told the element that a field in its BorderThickness
property has changed. You need to notify the element that its BorderThickness
has changed, which you can only do by directly setting the dependency property to a new value - say, by making it the target of a binding to an object that does change notification.
It's something of a pain to make a view model for this, but once you do, it's done.
The window:
<Window x:Class="ThicknessDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=System" xmlns:ThicknessDemo="clr-namespace:ThicknessDemo" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ThicknessDemo:ThicknessViewModel x:Key="thickness" />
</Window.Resources>
<DockPanel DataContext="{StaticResource thickness}">
<Border DockPanel.Dock="Top"
Width="100"
Height="50"
Margin="5"
BorderBrush="Blue"
BorderThickness="{Binding Thickness}" />
<TextBox DockPanel.Dock="Top"
Text="{Binding Left, Mode=TwoWay}" />
<TextBox DockPanel.Dock="Top"
Text="{Binding Right, Mode=TwoWay}" />
<TextBox DockPanel.Dock="Top"
Text="{Binding Top, Mode=TwoWay}" />
<TextBox DockPanel.Dock="Top"
Text="{Binding Bottom, Mode=TwoWay}" />
<TextBlock DockPanel.Dock="Top" />
</DockPanel>
</Window>
The view model:
public class ThicknessViewModel : INotifyPropertyChanged
{
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler h = PropertyChanged;
if (h != null)
{
h(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ThicknessViewModel()
{
_Thickness = new Thickness(1, 1, 1, 1);
}
private Thickness _Thickness;
public Thickness Thickness { get { return _Thickness; } set { _Thickness = value;} }
public double Left
{
get { return _Thickness.Left; }
set
{
_Thickness.Left = value;
OnPropertyChanged("Thickness");
}
}
public double Right
{
get { return _Thickness.Right; }
set
{
_Thickness.Right = value;
OnPropertyChanged("Thickness");
}
}
public double Top
{
get { return _Thickness.Top; }
set
{
_Thickness.Top = value;
OnPropertyChanged("Thickness");
}
}
public double Bottom
{
get { return _Thickness.Bottom; }
set
{
_Thickness.Bottom = value;
OnPropertyChanged("Thickness");
}
}
}
I believe this will point you in the right direction: Read halfway down it has 2 ways of approaching this, one with a converter and one without.
http://10rem.net/blog/2010/05/08/breaking-apart-the-margin-property-in-xaml-for-better-binding
The simplest solution for me turns out to be to just listen to the TextChanged event of the TextBox, and replace the BorderThickness in code behind.
MainWindow.xaml:
<Window
x:Class="BorderThicknessBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:BorderThicknessBindingTest="clr-namespace:BorderThicknessBindingTest"
Height="300" Width="500">
<Grid Margin="10">
<Border
x:Name="MyBorder"
BorderBrush="Black"
Background="AliceBlue"
BorderThickness="3"/>
<TextBox
x:Name="MyTextBox"
HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=BorderThickness.Left, ElementName=MyBorder, Mode=OneWay}"/>
</Grid>
</Window>
MainWindow.xaml.cs, in the constructor:
MyTextBox.TextChanged += (sender, e) =>
{
double d;
if (!double.TryParse(MyTextBox.Text, out d)) return;
var t = MyBorder.BorderThickness;
t.Left = d;
MyBorder.BorderThickness = t;
};
Right now this works for me, Robert Rossney's solution is better.
精彩评论