WPF控件中只绑定Margin属性的一部分

62

我有这个:

<TabControl Margin="0,24,0,0">...</TabControl>

我想绑定TabControl的"Top"部分,直觉上我会这样做:

<TabControl Margin="0,{Binding ElementName=TheMenu, Path=Height},0,0">
 ...
</TabControl>

我该如何做呢?

7个回答

57

你尝试过使用这样的转换器吗?

在 VB.Net 中

Public Class MarginConverter
  Implements IValueConverter

  Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
    Return New Thickness(0, CDbl(value), 0, 0)
  End Function

  Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
    Return Nothing
  End Function
End Class

或者在C#中

public class MarginConverter : IValueConverter
{

    public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return new Thickness(0, System.Convert.ToDouble(value), 0, 0);
    }

    public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}

XAML

<Window.Resources>
    <local:MarginConverter x:Key="marginConverter"></local:MarginConverter>
</Window.Resources>
<Grid>
    <StackPanel>
        <Slider Name="Slider1"></Slider>
        <TabControl Name="TabControl" Margin="{Binding ElementName=Slider1, Path=Value, Converter={StaticResource marginConverter}}">
            <Button>Some content</Button>
        </TabControl>
    </StackPanel>
</Grid>

注意:
使用MultiConverter

在运行时获取四个值并使用MultiValueConverter也是可行的。 Thickness对象的Top属性不是Dependency-Object,因此您不能对其进行绑定(除非您的源不是Dependency-Object)。

XAML

<Window.Resources>
    <local:MarginConverter x:Key="marginConverter"></local:MarginConverter>
    <local:MultiMarginConverter x:Key="multiMarginConverter"></local:MultiMarginConverter>
</Window.Resources>
<Grid>
    <StackPanel>
        <Slider Name="Slider1"></Slider>
        <Slider Name="Slider2"></Slider>
        <Slider Name="Slider3"></Slider>
        <Slider Name="Slider4"></Slider>
        <TabControl Name="TabControl">
            <TabControl.Margin>
                <MultiBinding Converter="{StaticResource multiMarginConverter}">
                    <Binding ElementName="Slider1" Path="Value"></Binding>
                    <Binding ElementName="Slider2" Path="Value"></Binding>
                    <Binding ElementName="Slider3" Path="Value"></Binding>
                    <Binding ElementName="Slider4" Path="Value"></Binding>
                </MultiBinding>
            </TabControl.Margin>
            <Button>Some content</Button>
        </TabControl>
    </StackPanel>
</Grid>

...和C#

  class MultiMarginConverter : IMultiValueConverter
  {
    public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      return new Thickness(System.Convert.ToDouble(values[0]),
                           System.Convert.ToDouble(values[1]),
                           System.Convert.ToDouble(values[2]),
                           System.Convert.ToDouble(values[3]));
    }

    public object[] ConvertBack(object value, System.Type[] targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      return null;
    }
  }

编辑(2) 反向绑定:
我不确定这会让你感到满意。依我之见,我会尽量避免这种情况,但好吧……如果您的源是一个依赖属性,您可以将其绑定到外边距(Margin)。

<Slider Name="Slider5" Minimum="-99" Maximum="0" Value="{Binding ElementName=TabControl, Path=Margin.Top, Mode=OneWayToSource}"></Slider>

但是我用这个做了一些效果。
诀窍在于,你不是将TabControl的Margin的一部分绑定到“其他地方”,而是将“其他地方”绑定到TabControl的Margin,并指定Binding-Mode为 OneWayToSource 。


4
但这种方法强制我在代码中输入所有其他数字,有没有一种使用子标记的标记语言的方法,例如:<TabControl.Margin.Thikness.Top="{Binding...}"...> - Tar
1
请参见编辑内容,使用MultiValueConverter进行建议。Top(和Bottom等)不是依赖属性。对于绑定,至少有一个部分必须是依赖属性。 - Markus
1
我注意到当给绑定添加FallBackValue时,它会在传递给MultiMarginConverter之前被转换为Thickness,从而导致出错。有什么方法可以防止这种情况发生,还是我必须在转换器中将其拆开? - Patrick

29

实际上,控件的Margin属性是Thickness类型的。因此,如果类型为Thickness,则可以将其绑定到属性。

 public Thickness LeftMargin { get; set; }

你也可以设置Thickness对象的一部分。例如 -

 LeftMargin = new Thickness(20,0,0,0);

Xaml中,我们可以直接将此属性绑定到任何元素的边距属性...就像这样。

 <TextBlock Text="Some Text"  Margin="{Binding LeftMargin}"  />

1
我认为如果你只需要绑定一个方向,这是正确的方法。否则,如果你需要控件边距每个方向不同的值,就需要其他选项了。 - heltonbiker
2
@heltonbiker 我认为完全没问题,你可以单独设置左,右,上和下,具有不同的值。 - CularBytes
@CularBytes 我想他的意思是,如果你需要将每一边绑定到不同的属性。这不能仅通过一个 Margin 属性实现。就我所知,使用 MultiBinding 是唯一的方法。 - Martin Schneider
查看我的答案,了解绑定多个边距属性的简单解决方案。 - bugged87

12
你可以尝试类似这个问题中的答案。
该解决方案使用一个附加属性,允许使用以下XAML代码:
<Button ap:MoreProps.MarginRight="10" />

这个附加属性同样由一个 DependencyObject 支持,所以数据绑定可以正常工作。


1
这是最好的答案,允许累积独立边距绑定,并且在我阅读的所有相关线程中,它是唯一提供此功能的。 - Shay
1
比顶级答案好得多,因为它是一种自然、逻辑的方法。谢谢! - ElDoRado1239

5

我曾使用这种方法来处理StackPanel中的左侧边距,好处是不需要任何转换器。

<DockPanel VerticalAlignment="Top">
  <TextBlock Name="tbkFulltextCaption"
             Text="Static Caption:"
             DockPanel.Dock="Left" />
  <StackPanel Orientation="Horizontal"
              DockPanel.Dock="Bottom">
      <FrameworkElement Name="feLeftMargin"
                        Width="{Binding Width, ElementName=tbkFulltextCaption, Mode=OneWay}" />
      <TextBlock Text="(some text with margin of tbkFulltextCaption.Width)"
                 Name="tbkUnderNonsense" 
                 FontSize="8"                                       
                 Foreground="Gray">
      </TextBlock>
  </StackPanel>
  <TextBox Name="tbFulltextSearch" />
</DockPanel>

preview


4

从你的代码中,我看出你的菜单和选项卡可能会重叠,所以你想使用边距来分隔它们。我觉得这个做法就像是两列CSS布局。

回到重点,我认为你可以将 TranslateFransform 应用到 TabControl.RenderTransform 上。你可以绑定 Y 属性。


3
为了完善Ioop的方法,提供一个属性来控制边距,而不是使用转换器(如果您没有连接到另一个WPF元素):
创建4个标准属性和一个只读属性,如下所示-
Public Class CustomMargin
    Implements INotifyPropertyChanged

    Private _Left As Double
    Private _Right As Double
    Private _Up As Double
    Private _Down As Double

    Public Sub New()
      _Up = 0
      _Down = 0
      _Left = 0
      _Right = 0
    End Sub

    Public Sub New(Vertical as Double, Horizontal as Double)
      _Up = Vertical
      _Down = Vertical
      _Left = Horizontal
      _Right = Horizontal
    End Sub

    Public Sub New(Left as Double, Up as Double, Right as Double, Down as Double)
      _Up = Up
      _Down = Down
      _Left = Left
      _Right = Right
    End Sub

    Public Property Left As Double
        Get
            Return _Left
        End Get
        Set(value As Double)
            _Left = value
            OnPropertyChanged(New PropertyChangedEventArgs("MyMargin"))
        End Set
    End Property

    Public Property Right As Double
        Get
            Return _Right
        End Get
        Set(value As Double)
            _Right = value
            OnPropertyChanged(New PropertyChangedEventArgs("MyMargin"))
        End Set
    End Property

    Public Property Up As Double
        Get
            Return _Up
        End Get
        Set(value As Double)
            _Up = value
            OnPropertyChanged(New PropertyChangedEventArgs("MyMargin"))
        End Set
    End Property

    Public Property Down As Double
        Get
            Return _Down
        End Get
        Set(value As Double)
            _Down = value
            OnPropertyChanged(New PropertyChangedEventArgs("MyMargin"))
        End Set
    End Property

    Public ReadOnly Property MyMargin As Thickness
        Get
            Return New Thickness(Left, Up, Right, Down)
        End Get
    End Property

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
        If Not PropertyChangedEvent Is Nothing Then
            RaiseEvent PropertyChanged(Me, e)
        End If
    End Sub
End Class

那么,您只需要添加 XAML -
<Label x:Name="MyLabel" Margin="{Binding Path=MyMargin, FallbackValue=0 0 0 0, Mode=OneWay}"/>

然后在WPF窗口的代码后端-

Private _NewMargin as New CustomMargin

Public Sub New()
  InitializeComponent()
  MyLabel.DataContext = _NewMargin
End Sub

从那里开始,您可以使用任何控件来单独更改所有4个边距,可重用于其他控件。


2

好的,虽然这是旧内容,但我正在寻找更好的方法:

<TabControl>
    <TabControl.Margin>
        <Thickness Top="{Binding ElementName=TheMenu, Path=Height}" />
     </TabControl.Margin>
</TabControl>

2
这个不起作用,你会得到以下错误信息:"无法在'Thickness'类型的'Top'属性上设置'Binding'。" - Paul
当然会有不同的原因……也许你可以发布你的代码。 - Athanviel
1
@Paul 是正确的,你不能将绑定设置到 Thickness 的 Top 属性,因为它不是一个 DependencyProperty,而且 Thickness 也不是一个 DependencyObject。 - Tolly
我过去使用过它 - 框架有什么变化吗? - Athanviel
@Athanviel 我已经尝试了所有的方法,甚至回溯到.NET Framework 3.0(支持WPF的最早版本),但即使那时也没有成功。 - Tolly
感谢您的查看!很遗憾,我使用它的代码在我的前公司 - 我很想检查。也许有一个插件可以使它工作 :/ 显然,它为一些人工作,这使得他们投了3个赞。 - Athanviel

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