繁体   English   中英

如何仅在XAML中设置上边距?

[英]How to set a top margin only in XAML?

我可以在代码中单独设置边距,但是如何在XAML中设置边距,例如我该怎么做:

伪代码:

<StackPanel Margin.Top="{Binding TopMargin}">

这不是你想要的吗?

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

第一个值是左边距,然后是顶部,然后是右边,最后一个但不是最小值。

我不确定你是否想将它绑定到某个东西,但如果没有,那就行了。

关键是要意识到在代码中设置它是这样的:

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

相当于:

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

不能通过 代码或XAMLThickness实例中仅设置单个值。 如果您没有设置某些值,它们将隐式为零。 因此,您可以这样做将您在其他问题中接受的代码示例转换为XAML等效项:

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

其中MyConverter只返回一个仅设置TopThickness ,并将所有其他值保留为零。

当然,你可以写自己的控制, 公开为依赖属性,这些单独的值,使你的代码干净了一点:

<CustomBorder TopMargin="{Binding TopMargin}">
</CustomBorder>

比自定义控件更好的选择是编写附加属性并使用依赖项属性设置器中的上述代码更改厚度。 以下代码可用于具有保证金的所有控件。

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
}

如果您将此与行为结合使用,则可以在TopMargin属性上获得通知更改。

这属于WPF修正案:

  1. 我是WPF,你会在编写Windows应用程序时使用我 - 最终。
  2. 不要使用其他技术 - 我不会跨平台,但我会尝试使用SL。
  3. 如果您打算使用我 - 请确保您知道自己在做什么。
  4. 每7天或几小时或几分钟的编码,我会让你休息一下去。
  5. 尊重窗体。
  6. MVVM - > INPC,INCC - >您可以使用它,也可以将它与愤怒一起使用 - 您的选择!
  7. 不要互操作其他应用程序。
  8. 您也应支付混合费用。
  9. 您无法使用附加属性或边距的绑定动态设置元素的位置,而无需在后面写几行代码。

  10. 不要将此技术与其他技术进行比较。

您的问题列在#9。

您不能仅使用绑定定义上边距,因为Margin属于Thickness类型,而不是依赖项对象。 但是,您可以使用MultiValueConverter ,它将采用4个边距值来制作1个厚度对象

转换器:

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>

刚写了一些附加属性,可以很容易地从绑定或静态资源设置单个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的框架。 好处是它们不会覆盖边距上的其他值,因此您也可以将它们组合在一起。

使用转换器,下面的示例代码会将您绑定的double转换为厚度。 它会将厚度的“顶部”设置为边界字段。 您可以选择使用ConverterParameter来确定是否绑定到left,top,right或bottom。

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

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

    //etc...

也许我“迟到了”,但不喜欢任何提供的解决方案,在我看来,最简单和最干净的解决方案是在ViewModel(或任何你绑定的)中定义Thickness属性,然后绑定该属性。 像这样的东西:

public class ItemViewModel
{
  public Thickness Margin { get; private set }

  public ItemViewModel(ModelClass model)
  {
    /// You can calculate needed margin here, 
    /// probably depending on some value from the Model
    this.Margin = new Thickness(0,model.TopMargin,0,0);
  }
}

然后XAML很简单:

<StackPanel Margin="{Binding Margin}">

这是一种简单的方法,无需编写转换器或硬编码边距值。 首先,在Window(或其他控件)资源中定义以下内容:

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

现在,如果要更改控件之间的默认空间,只需在一个位置更改它。

我使用绑定到Margin(RelativeSource Self)和解析ConverterParameter的ValueConverter,给出为“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覆盖。

玩得开心!

这是一个很好的解决方案:

        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>

然后,您可以创建像这样的小,小,中,大和巨大的边距:

local:Nifty.Margin="H000"

要么

local:Nifty.Margin="_S_S"

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

我以为你可以使用MSDN中的属性语法:

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

比你不需要任何转换器

但Top不是DependancyProperty - 回到转换器

最好的方法是通过指定类似下面的代码示例来做到这一点。

<StackPanel Margin=",10,,">

不幸的是,默认情况下,这种功能似乎并不存在于WPF中,这是一种耻辱,因为它要求开发人员以某种方式对已知的默认值进行硬编码,以后使应用程序的外观或主题变得更加困难。

在这一点上我能想到的最佳解决方案是使用转换器,但是为了引入这个转换器而必须生成的额外代码量并不理想。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM