18

This is quite easy to do from code-behind:

var button = new Button();
var margin = button.Margin;
margin.Right = 10;
button.Margin = margin;

In XAML, however, I'm limited to the following:

<Button Margin="0,0,10,0" />

The problem with this is that now I've potentially overwritten the other margin values (i.e. left, top, bottom) by setting them to zero).

Is there any way to have XAML like the following?

<Button MarginRight="10" />
bugged87
  • 3,026
  • 2
  • 26
  • 42

4 Answers4

17

An attached property could be used. In fact, this is exactly the purpose of attached properties: accessing parent element properties or adding additional functionality to a specific element.

For example, define the following class somewhere in your application:

using System;
using System.Windows;
using System.Windows.Controls;

namespace YourApp.AttachedProperties
{
    public class MoreProps
    {
        public static readonly DependencyProperty MarginRightProperty = DependencyProperty.RegisterAttached(
            "MarginRight",
            typeof(string),
            typeof(MoreProps),
            new UIPropertyMetadata(OnMarginRightPropertyChanged));

        public static string GetMarginRight(FrameworkElement element)
        {
            return (string)element.GetValue(MarginRightProperty);
        }

        public static void SetMarginRight(FrameworkElement element, string value)
        {
            element.SetValue(MarginRightProperty, value);
        }

        private static void OnMarginRightPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            var element = obj as FrameworkElement;

            if (element != null)
            {
                int value;
                if (Int32.TryParse((string)args.NewValue, out value))
                {
                    var margin = element.Margin;
                    margin.Right = value;
                    element.Margin = margin;
                }
            }
        }
    }
}

Now in your XAML all you must do is declare the following namespace:

xmlns:ap="clr-namespace:YourApp.AttachedProperties"

And then you can write XAML such as the following:

<Button ap:MoreProps.MarginRight="10" />



Alternatively, you can avoid using an attached property and instead write some slightly more lengthy XAML such as:

<Button>
    <Button.Margin>
        <Thickness Right="10" />
    </Button.Margin>
</Button>

bugged87
  • 3,026
  • 2
  • 26
  • 42
  • 4
    That XAML at the end is the same thing as `Margin="0,0,10,0"` as you overwrite the existing `Thickness` if there is one. – H.B. Sep 28 '12 at 02:01
  • 2
    @H.B. Thanks, I should've have tested that last part. You're right. The whole margin would be reassigned and any unspecified values would be returned to default. I've edited the answer to flag that part. – bugged87 Sep 28 '12 at 03:10
  • unfortunately this does not work, at the time when the attached property is set the Margin is not set yet, so I modify the default 0 margin and not the one specified in XAML – Matus Mar 05 '21 at 14:10
1

Although an attatched property can work. I would try to refactor your code so you are not making UI changes in the the code behind. You should handle as much of the UI as you can inside the design side of the file. I try to use the codebehind of xaml files as little as possible because it causes issues with MVVM.

kbo4sho88
  • 175
  • 7
  • 1
    Do you have any suggestions for refactoring the code? If the XAML designer lacks required functionality, then it seems like code-behind is a great place to add extra features. Keep in mind that an attached property defined and maintained in a single place is not really the same as writing that code in the code-behind of each XAML file. I guess it also depends on exactly what you're doing in the code-behind. If you're modifying the data context, then yes you may be causing issues for the MVVM pattern. However, if you're only modifying UI components then it's probably fine. – bugged87 Sep 28 '12 at 14:28
  • 1
    This I've always had an issue with... the code-behind is a partial class with the XAML code... it's at the same level as the XAML. If it can be used to do just UI related activities, it's no different then doing it in XAML. – AshbyEngineer Apr 11 '17 at 15:40
1

You could data bind the Margin to a property (string) in your MVVM. In your MVVM, all you need is to track the individual properties (top, right, bottom, right).

You can use converters as in: How to set a top margin only in XAML? or Binding only part of the margin property of WPF control

Community
  • 1
  • 1
Patrice Calvé
  • 674
  • 6
  • 12
  • Data binding to a property in my view model would then mean that my view model would have to maintain a value that is specific to UI design. This breaks the MVVM pattern. – bugged87 Sep 28 '12 at 14:21
  • I totally agree with you... Where do we draw the line between "UI" specific functionality, like colouring rows based on status. If there's "logic" involved and the code relies on "other" properties like service results and/or properties, then MVVM it is... Or, you can always attach behaviours. – Patrice Calvé Sep 28 '12 at 15:24
0

You are wrong with this part:

var button = new Button();
button.Margin.Right = 10;

error CS1612: Cannot modify the return value of 'System.Windows.FrameworkElement.Margin' because it is not a variable

Is already not valid code, because Margin returns a struct and is therefore a value type. And because it doesn't derive from DependencyObject a lot of DataBinding tricks also won't work.

I just wanted to give a proper explanation, otherwise i would say your first answer is pretty much the only way.

dowhilefor
  • 10,971
  • 3
  • 28
  • 45
  • @ dowhilefor You are correct about the code-behind part. Typo on my part. I've updated my question to reflect what I actually meant. However, DataBinding will work because the MarginRight property IS a DependencyObject. Therefore, whenever the property value is changed via binding then the Margin will be adjusted accordingly in the callback method. – bugged87 Sep 28 '12 at 01:31
  • @bugged87 what i meant with the databinding, was the original question, not your attached property answer. Of course there works databinding. But on a struct, which Thickness is, you can't use databinding unless its the Source of course. In short i wasn't talking about MarginRight, instead i was talking about Margin.Right. – dowhilefor Sep 28 '12 at 13:30