Set a margin from a binding

18,278

Solution 1

Return the margin?

public Thickness Margin
{
    get { return new Thickness(BondIndent,0,BondIndent,0);}
}

Then change:

<Image x:Name="_image" Source="mat.png" Margin="{Binding EditorRow.Margin}" />

Solution 2

You probably need to use a ValueConverter for this. Something like:

public class LeftRightThicknessConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is int)
        {
            int margin = (int)value;
            return Thickness(margin, 0, margin, 0);
        }
        return Thickness();
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

You can then use the converter in the following way:

<Grid>
    <Grid.Resources>
        <xxx:LeftRightThicknessConverter x:Key="LeftRightThicknessConverter" />
    </Grid.Resources>

    <Image Margin="{Binding SomePropertyPath, Converter={StaticResource LeftRightThicknessConverter}}" />
</Grid>

Assuming that xxx is a valid xml-namespace.

Solution 3

Instead of returning an int you can return a Thickness, which the Margin actually is:

public Thickness BondIndent
{
    get
    {
        int margin = _bondSequence * 5;
        return new Thickness(margin, 0, margin, 0);
    }
}

The reason why your example works is because Thickness has overloaded constructors that take 1, 2 or 4 arguments. Whenthe constructor that takes 1 argument is called, all sides are initialized to that value. WPF automatically converts this to a Thickness based on the bound value.

On another topic, BondIndent might better be called BondMargin or BondThickness now.

Solution 4

Just wrote some attached properties that should make it easy to set an individual Margin value from a binding or static resource:

WPF:

public class Margin
{
    public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached(
        "Left",
        typeof(double),
        typeof(Margin),
        new PropertyMetadata(0.0, LeftChanged));

    private static void LeftChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var frameworkElement = d as FrameworkElement;
        if (frameworkElement != null)
        {
            Thickness currentMargin = frameworkElement.Margin;
            frameworkElement.Margin = new Thickness((double)e.NewValue, currentMargin.Top, currentMargin.Right, currentMargin.Bottom);
        }
    }

    public static void SetLeft(UIElement element, double value)
    {
        element.SetValue(LeftProperty, value);
    }

    public static double GetLeft(UIElement element)
    {
        return 0;
    }

    public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached(
        "Top",
        typeof(double),
        typeof(Margin),
        new PropertyMetadata(0.0, TopChanged));

    private static void TopChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var frameworkElement = d as FrameworkElement;
        if (frameworkElement != null)
        {
            Thickness currentMargin = frameworkElement.Margin;
            frameworkElement.Margin = new Thickness(currentMargin.Left, (double)e.NewValue, currentMargin.Right, currentMargin.Bottom);
        }
    }

    public static void SetTop(UIElement element, double value)
    {
        element.SetValue(TopProperty, value);
    }

    public static double GetTop(UIElement element)
    {
        return 0;
    }

    public static readonly DependencyProperty RightProperty = DependencyProperty.RegisterAttached(
        "Right",
        typeof(double),
        typeof(Margin),
        new PropertyMetadata(0.0, RightChanged));

    private static void RightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var frameworkElement = d as FrameworkElement;
        if (frameworkElement != null)
        {
            Thickness currentMargin = frameworkElement.Margin;
            frameworkElement.Margin = new Thickness(currentMargin.Left, currentMargin.Top, (double)e.NewValue, currentMargin.Bottom);
        }
    }

    public static void SetRight(UIElement element, double value)
    {
        element.SetValue(RightProperty, value);
    }

    public static double GetRight(UIElement element)
    {
        return 0;
    }

    public static readonly DependencyProperty BottomProperty = DependencyProperty.RegisterAttached(
        "Bottom",
        typeof(double),
        typeof(Margin),
        new PropertyMetadata(0.0, BottomChanged));

    private static void BottomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var frameworkElement = d as FrameworkElement;
        if (frameworkElement != null)
        {
            Thickness currentMargin = frameworkElement.Margin;
            frameworkElement.Margin = new Thickness(currentMargin.Left, currentMargin.Top, currentMargin.Right, (double)e.NewValue);
        }
    }

    public static void SetBottom(UIElement element, double value)
    {
        element.SetValue(BottomProperty, value);
    }

    public static double GetBottom(UIElement element)
    {
        return 0;
    }
}

UWP:

public class Margin
{
    public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached(
        "Left",
        typeof(double),
        typeof(Margin),
        new PropertyMetadata(0.0));

    public static void SetLeft(UIElement element, double value)
    {
        var frameworkElement = element as FrameworkElement;
        if (frameworkElement != null)
        {
            Thickness currentMargin = frameworkElement.Margin;

            frameworkElement.Margin = new Thickness(value, currentMargin.Top, currentMargin.Right, currentMargin.Bottom);
        }
    }

    public static double GetLeft(UIElement element)
    {
        return 0;
    }

    public static readonly DependencyProperty TopProperty = DependencyProperty.RegisterAttached(
        "Top",
        typeof(double),
        typeof(Margin),
        new PropertyMetadata(0.0));

    public static void SetTop(UIElement element, double value)
    {
        var frameworkElement = element as FrameworkElement;
        if (frameworkElement != null)
        {
            Thickness currentMargin = frameworkElement.Margin;

            frameworkElement.Margin = new Thickness(currentMargin.Left, value, currentMargin.Right, currentMargin.Bottom);
        }
    }

    public static double GetTop(UIElement element)
    {
        return 0;
    }

    public static readonly DependencyProperty RightProperty = DependencyProperty.RegisterAttached(
        "Right",
        typeof(double),
        typeof(Margin),
        new PropertyMetadata(0.0));

    public static void SetRight(UIElement element, double value)
    {
        var frameworkElement = element as FrameworkElement;
        if (frameworkElement != null)
        {
            Thickness currentMargin = frameworkElement.Margin;

            frameworkElement.Margin = new Thickness(currentMargin.Left, currentMargin.Top, value, currentMargin.Bottom);
        }
    }

    public static double GetRight(UIElement element)
    {
        return 0;
    }

    public static readonly DependencyProperty BottomProperty = DependencyProperty.RegisterAttached(
        "Bottom",
        typeof(double),
        typeof(Margin),
        new PropertyMetadata(0.0));

    public static void SetBottom(UIElement element, double value)
    {
        var frameworkElement = element as FrameworkElement;
        if (frameworkElement != null)
        {
            Thickness currentMargin = frameworkElement.Margin;

            frameworkElement.Margin = new Thickness(currentMargin.Left, currentMargin.Top, currentMargin.Right, value);
        }
    }

    public static double GetBottom(UIElement element)
    {
        return 0;
    }
}

Usage:

<TextBlock Text="Test"
    app:Margin.Top="{Binding MyValue}"
    app:Margin.Right="{StaticResource MyResource}"
    app:Margin.Bottom="20" />

The nice thing is they won't override the other values on the Margin, so you can combine them as well.

Share:
18,278
user589195
Author by

user589195

Updated on June 01, 2022

Comments

  • user589195
    user589195 almost 2 years

    I have a binding value that returns a int that represents a value I wasnt to assign to left and right margins of an element.

    Heres what I've tried but it wont compile.

    It works if I set the entire margin, but I only want left and right.

    Xaml:

    <Image x:Name="_image" Source="mat.png" Margin="{Binding EditorRow.BondIndent},0,{Binding EditorRow.BondIndent},0" />
    

    class:

    public int BondIndent
    {
        get { return _bondSequence * 5; }
    }
    
    • default
      default over 11 years
      How about returning a Thickness instead?
    • user589195
      user589195 over 11 years
      Default as I have control over what is returned from the class behind. Please could you add an answer based on returning a thickness and I will mark as answer
    • default
      default over 11 years
      added an answer instead. HTH
  • default
    default over 11 years
    How does the OP use this valueconverter?
  • user589195
    user589195 over 11 years
    Can the value converters be static and stored in another assembly?
  • odyss-jii
    odyss-jii over 11 years
    You can have the definition of the converter class in another assembly. But you need an instance accessible from the XAML. Typically, you would create an instance like in the example above in the resources of an element (a UserControl element for example). You can define xml namespaces which refer to your assembly (and specific namespace) by adding xmlns:xxx="clr-namespace:MyOtherAssembly.TheNamespace;assemb‌​ly=MyOtherAssembly" to the root node of the usercontrol or templated control. Replace xxx which some appropriate name of course.
  • default
    default over 11 years
    A suggestion would be to use as operator instead of is + cast
  • Daniel
    Daniel over 11 years
    @odyss-jii Since the OP isn't using MVVM, I don't see a reason to decouple to this degree. The nature of his property assumes the DataContext for the view is making decisions about how things should look... Which means a better (simpler) solution would just be to return a Thickness.
  • odyss-jii
    odyss-jii over 11 years
    @Doc That may well be the case, but I wished to preserve the OP's data model.
  • user589195
    user589195 over 11 years
    Thanks for this odyss - Would of worked fine but since I have control of the return type from the class I am just returning a thickness instead of an int. Useful example for me on how to use valueconverter aswell.
  • Nicolas
    Nicolas over 7 years
    setters and getters aren't called directly. You have to add a PropertyCallback to the DependencyProperty. I can't really believe, that you tested it :)
  • RandomEngy
    RandomEngy over 7 years
    @Nicolas msdn.microsoft.com/en-us/library/ms749011(v=vs.110).aspx Scroll down to "Custom Attached Properties" -> "How to create an attached property". They use naming conventions for the getter and setter methods and you don't need to supply a property callback on the DependencyProperty. We've been using this all over our code; it does work.
  • Nicolas
    Nicolas over 7 years
    Blend and Visual Studio Designer use those functions, but WPF not. Those methods are just interesting to get/set the properties through the code behind...but you set those properties in XAML in your example (not code behind). Because WPF is managing the information, you should not put logic into those methods, besides setting and getting the information.
  • RandomEngy
    RandomEngy over 7 years
    @Nicolas Ahh, WPF works a bit differently. Added a version of the attached property that works there.