views:

27

answers:

3

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">
        <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?

+1  A: 

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

halfevil
+2  A: 

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");
        }
    }
}
Robert Rossney
A: 

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.

eriksmith200