如何在XAML中仅设置顶部边距?

52

我可以在代码中单独设置边距,但是在XAML中应该如何实现呢?例如,我该如何做到这一点:

伪代码:

<StackPanel Margin.Top="{Binding TopMargin}">
12个回答

58

这难道不是你正在寻找的吗?

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

第一个值是左边距,接着是上边距,然后右边距,最后是下边距。

我不确定你是否想将其绑定到某个东西上,但如果不需要,那么它就可以工作。


5
不行,这会把“Left/Right/Bottom”都设置为“0”。OP希望保留现有的边距(可能来自样式绑定)并仅设置一个边。我也是这样希望的,但似乎需要付出相当大的努力才能实现。 - Daniel

46

关键在于要意识到像这样在代码中设置:

sp2.Margin = new System.Windows.Thickness{ Left = 5 };

等同于:

sp2.Margin = new System.Windows.Thickness{ Left = 5, Top = 0, Right = 0, Bottom = 0 };

无论是在代码还是XAML中,都不能只设置Thickness实例的单个值。如果您没有设置某些值,它们将被隐式设置为零。因此,您只需执行以下操作即可将其他问题中接受的代码示例转换为XAML等效代码:

<StackPanel Margin="{Binding TopMargin, Converter={StaticResource MyConverter}}"/>

其中MyConverter只返回一个将Top设置为非零值,而将所有其他值设置为零的Thickness

当然,您可以编写自己的控件,将这些单独的值作为依赖属性公开,以使您的代码更加清晰:

<CustomBorder TopMargin="{Binding TopMargin}">
</CustomBorder>
比起自定义控件,更好的选择是编写一个附加属性,并在依赖属性的setter中使用上述代码来更改Thickness。下面的代码可用于所有具有Margin的控件。
public static readonly DependencyProperty TopMarginProperty =
    DependencyProperty.RegisterAttached("TopMargin", typeof(int), typeof(FrameworkElement),
                                        new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
public static void SetTopMargin(FrameworkElement element, int value)
{
    // set top margin in element.Margin
}
public static int GetTopMargin(FrameworkElement element)
{
    // get top margin from element.Margin
}
如果你将这个属性与Behavior相结合,就可以在TopMargin属性发生改变时得到通知。

2
你可以这样设置 var margin = sp2.Margin; margin.Left = 5; sp2.Margin = margin; 这将保留其他值不变。 - bugged87
2
@bugged87:OP想要在XAML中完成它。 - Kent Boogaart
@KentBoogaart,我知道问题所要求的内容,但我的评论是针对你的答案的。你说:“您无法通过代码或XAML仅设置Thickness实例中的单个值”。然而,您可以按照我建议的方式在代码中执行此操作。 - bugged87
@bugged87:你仍然在设置所有的值——只是从已有的值中进行了初始化。因此,我的观点依然成立。如果你没有从现有的Thickness实例中进行初始化,除了Left之外,其他所有值都将为0。也就是说,在这里没有“未设置”的TopBottomLeftRight的概念,因为它是一个值类型。它们要么由你的代码设置,要么隐式地设置为0。引用我自己的话,就是在你引用的那句话之后直接说的:“如果你不设置某些值,它们将被隐式地设置为零。” - Kent Boogaart
一个解决方案可以是 <StackPanel Margin="0,10,0,0" />,不需要绑定... - crazyDiamond
显示剩余2条评论

25

以下是WPF/XAML的戒律:

  1. 我是WPF/XAML,你在编写Windows应用时将使用我 - 最终。
  2. 除我之外,你不得使用任何其他技术 - 我可能不支持跨平台但是我会尝试使用 Silverlight UWP,因为Hololens将来会非常流行。"Xamarin.Forms"?没听说过!
  3. 当使用WPF/XAML时,你将反复地拿我的名字开玩笑并且会频繁出现问题。
  4. 记住安息日:每7天...或者编码每小时或分钟,我都会让你休息一下,到 StackOverflow2000things 去看一看。
  5. 尊敬你的父母:Windows Forms。
  6. 如果你采用MVVM,你也必须实现INPCINCC,但是请放心!你有选择:你可以使用它或者你可以带着愤怒来使用它。
  7. 你不可以与其他应用程序和框架进行互操作。
  8. 你不可以垂涎于邻居的UI框架。
  9. 你无法仅使用绑定(通过附加属性或边距)动态设置元素的位置,而不编写一些代码-behind。
  10. 在XAML中,你永远不会有一个简单的bool可见性属性。我是WPF/XAML。

你的罪行列在第9位。


6

我刚刚编写了一些附加属性,可以轻松地通过绑定或静态资源设置单个Margin值:

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;
    }
}

使用方法:

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

在UWP中进行了测试,但这应该适用于任何基于XAML的框架。好处是它们不会覆盖Margin上的其他值,因此您也可以将它们组合在一起。


我已经将您的精彩答案移植到Xamarin中:https://stackoverflow.com/questions/55704460/how-to-set-a-left-margin-only-in-xamarin-forms-xaml/55704461#55704461 - lucidbrot
为什么所有的 getter 都返回 0? - Mike Nakis
附加属性系统需要getter,但系统不会读取它们。此外,如果您需要从代码中获取边距,可以使用内置的Margin属性而不是附加属性。如果您有用例,可以实现getter以返回正确的值。 - RandomEngy

2

您无法仅使用绑定来定义顶部边距,因为MarginThickness类型,它不是依赖对象。但是,您可以使用MultiValueConverter,该转换器将采用4个边距值以创建1个Thickness对象。

转换器:

public class ThicknessMultiConverter : IMultiValueConverter
{
    #region IMultiValueConverter Members

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        double left = System.Convert.ToDouble(values[0]);
        double top = System.Convert.ToDouble(values[1]);
        double right = System.Convert.ToDouble(values[2]);
        double bottom = System.Convert.ToDouble(values[3]);
        return new Thickness(left, top, right, bottom);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        Thickness thickness = (Thickness)value;
        return new object[]
        {
            thickness.Left,
            thickness.Top,
            thickness.Right,
            thickness.Bottom
        };
    }

    #endregion
}

XAML :

<StackPanel>
    <StackPanel.Margin>
        <MultiBinding Converter="{StaticResource myThicknessConverter}">
            <Binding Path="LeftMargin"/>
            <Binding Path="TopMargin"/>
            <Binding Path="RightMargin"/>
            <Binding Path="BottomMargin"/>
        </MultiBinding>
    </StackPanel.Margin>
</StackPanel>

考虑到他想设置边距的单个部分并保留其他现有值不变,这该怎么做呢? - Ben M
好的,所有属性都必须在初始化时设置,但之后您只需要更改一个绑定属性即可... - Thomas Levesque
顺便说一句,你的解决方案也有完全相同的限制 ;) - Thomas Levesque
我知道,但我的代码更少。 :-) 另一方面,你的代码至少会在任何值更改时重新绑定... - Ben M
你可以在MultiBinding中添加一个ConverterParameter来指定你想要设置的值:例如,ConverterParameter="Top,Right"只需要两个绑定,并返回一个只有上边和右边边距设置的厚度。 - Bubblewrap
是的,但不设置其他变量相当于将它们设置为0(参见肯特的答案)。 - Thomas Levesque

1
这里有一种简单的方法,可以在不编写转换器或硬编码边距值的情况下完成。首先,在您的窗口(或其他控件)资源中定义以下内容:
<Window.Resources>
    <!-- Define the default amount of space -->
    <system:Double x:Key="Space">10.0</system:Double>

    <!-- Border space around a control -->
    <Thickness
        x:Key="BorderSpace"
        Left="{StaticResource Space}"
        Top="{StaticResource Space}"
        Right="{StaticResource Space}"
        Bottom="{StaticResource Space}"
        />

    <!-- Space between controls that are positioned vertically -->
    <Thickness
        x:Key="TopSpace"
        Top="{StaticResource Space}"
        />
</Window.Resources>

请注意,system被定义为xmlns:system="clr-namespace:System;assembly=mscorlib"
现在您可以按以下方式使用这些资源:
<Grid
    Margin="{StaticResource BorderSpace}"
    >
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Button
        Grid.Row="0"
        Content="Button 1"
        />

    <Button
        Grid.Row="1"
        Content="Button 2"
        Margin="{StaticResource TopSpace}"
        />
</Grid>

现在,如果您想更改控件之间的默认间距,只需要在一个地方进行更改即可。

1
我使用一个绑定到Margin(RelativeSource Self)的ValueConverter,并解析ConverterParameter,该参数为“top:123; left:456”。
转换器只覆盖由参数给出的边距。
public class MarginConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is Thickness)) return new Thickness();

        Thickness retMargin = (Thickness) value;
        List<string> singleMargins = (parameter as string)?.Split(';').ToList() ?? new List<string>();

        singleMargins.ForEach(m => {
                                  switch (m.Split(':').ToList()[0].ToLower().Trim()) {
                                      case "left":
                                          retMargin.Left = double.Parse(m.Split(':').ToList()[1].Trim());
                                          break;
                                      case "top":
                                          retMargin.Top = double.Parse(m.Split(':').ToList()[1].Trim());
                                          break;
                                      case "right":
                                          retMargin.Right = double.Parse(m.Split(':').ToList()[1].Trim());
                                          break;
                                      case "bottom":
                                          retMargin.Bottom = double.Parse(m.Split(':').ToList()[1].Trim());
                                          break;
                                  }
                              }
            );
        return retMargin;
    }

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

xaml

<TextBlock Margin="{Binding RelativeSource={RelativeSource Self}, 
                    Path=Margin, 
                    Converter={StaticResource MarginConverter}, 
                    ConverterParameter='top:0'}" 
Style="{StaticResource Header}" 
Text="My Header" />

TextBlock将使用Style给出的Margin,除了Margin-Top,它将被覆盖为0。

玩得开心!


1

我认为你可以使用属性语法,来自MSDN

      <object.Margin>
        <Thickness Top="{Binding Top}"/>
      </object.Margin>

那么您就不需要任何转换器了

但是顶部不是依赖项属性 - 回到转换器


1
您将会得到“无法设置只读属性 'System.Windows.Thickness.Top'” - Grigory
此外,这仍会使用默认值零覆盖其他值。因此,如果这不是您想要的行为,则即使使用硬编码值,此方法也无法实现。 - bugged87

0

这里有一个巧妙的解决方案:

        public class Nifty
    {
        private static double _tiny;
        private static double _small;
        private static double _medium;
        private static double _large;
        private static double _huge;
        private static bool _resourcesLoaded;

        #region Margins

        public static readonly DependencyProperty MarginProperty =
            DependencyProperty.RegisterAttached("Margin", typeof(string), typeof(Nifty),
                new PropertyMetadata(string.Empty,
                    new PropertyChangedCallback(OnMarginChanged)));

        public static Control GetMargin(DependencyObject d)
        {
            return (Control)d.GetValue(MarginProperty);
        }

        public static void SetMargin(DependencyObject d, string value)
        {
            d.SetValue(MarginProperty, value);
        }

        private static void OnMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement ctrl = d as FrameworkElement;
            if (ctrl == null)
                return;

            string Margin = (string)d.GetValue(MarginProperty);

            ctrl.Margin = ConvertToThickness(Margin);
        }

        private static Thickness ConvertToThickness(string Margin)
        {
            var result = new Thickness();

            if (!_resourcesLoaded)
            {
                _tiny = (double)Application.Current.FindResource("TinySpace");
                _small = (double)Application.Current.FindResource("SmallSpace");
                _medium = (double)Application.Current.FindResource("MediumSpace");
                _large = (double)Application.Current.FindResource("LargeSpace");
                _huge = (double)Application.Current.FindResource("HugeSpace");

                _resourcesLoaded = true;
            }

            result.Left = CharToThickness(Margin[0]);
            result.Top = CharToThickness(Margin[1]);
            result.Bottom = CharToThickness(Margin[2]);
            result.Right = CharToThickness(Margin[3]);

            return result;
        }


        private static double CharToThickness(char p)
        {
            switch (p)
            {
                case 't':
                case 'T':
                    return _tiny;
                case 's':
                case 'S':
                    return _small;
                case 'm':
                case 'M':
                    return _medium;
                case 'l':
                case 'L':
                    return _large;
                case 'h':
                case 'H':
                    return _huge;
                default:
                    return 0.0;
            }
        }

        #endregion

    }

如果您将此代码添加到您的命名空间并定义以下大小:
    <system:Double x:Key="TinySpace">2</system:Double>
<system:Double x:Key="SmallSpace">5</system:Double>
<system:Double x:Key="MediumSpace">10</system:Double>
<system:Double x:Key="LargeSpace">20</system:Double>
<system:Double x:Key="HugeSpace">20</system:Double>

你可以使用如下方式创建不同大小的边距:Tiny、Small、Medium、Large 和 Huge。
local:Nifty.Margin="H000"

或者

local:Nifty.Margin="_S_S"

代码将根据您的资源创建边距。

0

使用转换器,下面的示例代码将把您绑定到的双精度数值转换为厚度。它将把厚度的“顶部”设置为绑定字段。您还可以选择使用ConverterParameter来确定您是否绑定到左侧、顶部、右侧或底部。

<StackPanel Margin="{Binding TopMargin, Converter={StaticResource MyThicknessConverter}">

.

public class ThicknessSingleValueConverter : IValueConverter
{
    override Convert(...)
    {
         return new Thickness(0, (double)object, 0, 0);
    }

    //etc...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接