【问题标题】:How to set a top margin only in XAML?如何仅在 XAML 中设置上边距?
【发布时间】:2010-11-21 22:21:13
【问题描述】:

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

伪代码:

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

【问题讨论】:

    标签: c# wpf xaml margins


    【解决方案1】:

    这不是你要找的吗?

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

    第一个值是左边距,然后是顶部,然后是右侧,最后但并非最不重要的底部。

    我不确定你是否想将它绑定到某个东西,但如果没有,那会起作用。

    【讨论】:

    • 没有。这会用“0”覆盖左/右/下。 OP 想要保留现有边距(可能来自样式绑定)并仅设置一侧。和我一样,但看来你必须付出相当多的努力才能做到。
    【解决方案2】:

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

    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 只返回一个Thickness,它只设置Top 并将所有其他值保留为零。

    当然,您可以编写自己的控件,确实将这些单独的值公开为依赖属性,以使您的代码更简洁:

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

    比自定义控件更好的选择是编写附加属性并使用依赖属性设置器中的上述代码更改厚度。以下代码可用于所有具有 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 属性上获得通知更改。

    【讨论】:

    • 简单、有效,但我不知道为什么人们会尝试将其复杂化。一条线比一些人认为需要的 20 条左右要好得多。我很欣赏这个答案。
    • 你可以这样设置var margin = sp2.Margin; margin.Left = 5; sp2.Margin = margin;这将保持其他值不变。
    • @bugged87:OP 想在 XAML 中完成。
    • @KentBoogaart 我知道这个问题要问什么,但我的评论指的是你的回答。您说:“您不能通过代码或 XAML 在厚度实例中设置单个值”。但是,您可以按照我建议的方式在代码中执行此操作。
    • @bugged87:您仍在设置所有值 - 您只是从已经存在的内容中播种它们。因此,我的观点成立。如果您没有从现有实例中播种Thickness 实例,那么除了Left 之外的所有内容都将是0。 IE。这里没有“未设置”TopBottomLeftRight 的概念,因为它是一个值类型。它们由您的代码设置或隐式设置为0。也引用我自己的话,直接在你引用的那句话之后:“如果你不设置某些值,它们将隐式为零。”
    【解决方案3】:

    这属于 WPF 修正:

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

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

    您的问题列在#9。

    【讨论】:

      【解决方案4】:

      刚刚写了一些附加属性,应该可以很容易地从绑定或静态资源中设置单独的 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:stackoverflow.com/questions/55704460/…
      • 为什么所有的getter都返回0?
      • 附加属性系统需要getter,但系统不读取。此外,如果您需要从代码中获取边距,您可以使用内置的 Margin 属性而不是附加属性。如果您有一个用例,您可以实现 getter 以返回正确的值。
      【解决方案5】:

      您不能只使用绑定定义上边距,因为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>
      

      【讨论】:

      • 考虑到他想设置边距的一个部分并保持其他现有值不变,这将如何工作?
      • 好吧,所有属性都将在初始化时设置,但之后您只需更改绑定属性之一...
      • 顺便说一句,您的解决方案具有完全相同的限制;)
      • 我知道,但我的代码较少。 :-) OTOH,当任何值发生变化时,你的至少会重新绑定......
      • 您可以将 ConverterParameter 添加到 MultiBinding 以指定要设置的值:例如 ConverterParameter="Top,Right" 只需要两个绑定并返回仅包含顶部和右侧边距的厚度设置。
      【解决方案6】:

      这是一种无需编写转换器或硬编码边距值的简单方法。首先,在您的 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>
      

      现在如果要更改控件之间的默认间距,只需在一处更改即可。

      【讨论】:

        【解决方案7】:

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

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

        .

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

        【讨论】:

          【解决方案8】:

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

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

          你不需要任何转换器

          但顶部不是 DependancyProperty - 返回转换器

          【讨论】:

          • 你会得到“无法设置只读属性'System.Windows.Thickness.Top'”
          • 另外,这仍然会用默认零覆盖其他值。因此,如果这不是您想要的行为,那么即使使用硬编码值也不会起作用。
          【解决方案9】:

          这是一个不错的解决方案:

                  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"
          

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

          【讨论】:

            【解决方案10】:

            我使用绑定到 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 覆盖。

            玩得开心!

            【讨论】:

              【解决方案11】:

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

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

              【讨论】:

              • 厚度和边距是与视图相关的概念。因此,它们不属于正确的视图模型。
              【解决方案12】:

              如果能够通过指定类似下面的代码示例来做到这一点,那就太好了。

              <StackPanel Margin=",10,,">
              

              不幸的是,默认情况下 WPF 中似乎不存在此功能,这很遗憾,因为它要求开发人员对已知的默认值进行硬编码,这使得以后对应用程序进行皮肤或主题化变得更加困难。

              目前我能想到的最佳解决方案是使用转换器,但是您必须生成的额外代码量来引入它并不理想。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2018-07-31
                • 2014-12-11
                • 2021-11-19
                • 1970-01-01
                • 2015-12-06
                • 2014-04-11
                • 2011-10-25
                • 1970-01-01
                相关资源
                最近更新 更多