根据自定义控件的 DependencyProperty 设置 DockPanel.Dock 不起作用

Setting DockPanel.Dock according to DependencyProperty of a custom control does not work

我有一个枚举:

public enum ImageTextLocation 
{ 
   Left, 
   Top, 
   Right, 
   Bottom 
}

还有一个自定义 UserControl,其中包含该枚举的依赖项 属性。

<Button Content="{Binding ButtonText}"
        Command="{Binding Command}"
        Style="{StaticResource ImageButtonStyle}">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border x:Name="border"
                    Padding="{TemplateBinding Padding}"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                <DockPanel>
                    <ContentPresenter DockPanel.Dock="{Binding Path=TextLocation, Converter={StaticResource EnumToDockConvert}}" 
                                      Margin="2,6,0,2"/>
                </DockPanel>
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>
public partial class ImageButtonControl : UserControl
{ 
    public ImageTextLocation TextLocation
    {
        get => (ImageTextLocation)GetValue(TextLocationProperty);
        set => SetValue(TextLocationProperty, value); 
    }
    
    public static readonly DependencyProperty TextLocationProperty =
        DependencyProperty.Register(
            "TextLocation", 
            typeof(ImageTextLocation), 
            typeof(ImageButtonControl), 
            new FrameworkPropertyMetadata(ImageTextLocation.Left, 
                PropertyChangedCallback) { BindsTwoWayByDefault = true, }
        );
    
    private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var imageButton = (ImageButtonControl)d;
        var newLocation = (ImageTextLocation)e.NewValue;
        switch (newLocation)
        {
            case ImageTextLocation.Left: 
                 imageButton.SetCurrentValue(TextLocationProperty, ImageTextLocation.Left);
                 break;
            case ImageTextLocation.Right:
                 imageButton.SetCurrentValue(TextLocationProperty, ImageTextLocation.Right);
                 break;
        }
    }
}

我添加了一个转换器,可以将 ImageTextLocation 转换为 Dock:

public class EnumToDockConverter : IValueConverter
{
    public object Convert(object value, 
                          Type targetType, 
                          object parameter, 
                          CultureInfo culture)
    {
        if(value == null)
           return Dock.Left;
    
        ImageTextLocation location = (ImageTextLocation)value;
        var dock = Enum.Parse(typeof(Dock), location.ToString());
        return dock;
    }
    
    public object ConvertBack(object value, 
                              Type targetType, 
                              object parameter, 
                              CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

并将转换器实例添加到我的 UserControl:

的资源中
<coverter:EnumToDockConverter x:Key="EnumToDockConvert"/>

然后,我尝试使用 TextLocation="{Binding ImageTextPosition}" 设置自定义控件的 DockPanel.Dock,其中 ImageTextPosition 是我的视图模型的字符串 属性 绑定到 UserControl.

<controls:ImageButtonControl TextLocation="{Binding ImageTextPosition}"/>

当我设置依赖项时,DockPanel 的 Dock 没有设置 属性。

我认为不需要使用转换器。在您的 属性 更改回调中,直接调用 DockPanel.SetDock() 以更改内容演示器的停靠位置。我还将根据 Dock 定义 ImageTextLocation 以便您可以对其进行转换。

更新:OP 澄清了 ImageButtonControl 覆盖了 Button 的默认值 ControlTemplate,因此我更新了我的答案以在该场景下工作。直接从 Button 派生,而不是 UserControl,因此模板的内容无需跳过即可访问。

ImageButton.xaml

<!-- bound the content of the Button to TextLocation for testing -->
<Button x:Class="TestApp.ImageButton"
        Content="{Binding TextLocation, RelativeSource={RelativeSource Self}}" 
        Command="{Binding Command}">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border Padding="{TemplateBinding Padding}"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                <DockPanel>
                    <ContentPresenter x:Name="contentPresenter"
                                      Margin="2,6,0,2" 
                                      DockPanel.Dock="Left"/>

                    <!-- Put something the DockPanel for the content 
                         presenter to dock relative to. Presumably this 
                         would be an image -->
                    <Button Content="Main Content"/>
                </DockPanel>
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>

代码隐藏

public enum ImageTextLocation 
{ 
    Left = (int)Dock.Left,
    Top = (int)Dock.Top,
    Right = (int)Dock.Right,
    Bottom = (int)Dock.Bottom
}

public partial class ImageButton : Button
{
    private ContentPresenter _contentPresenter;

    public ImageButton()
    {
        InitializeComponent();
    }

    public ImageTextLocation TextLocation
    {
        get => (ImageTextLocation)GetValue(TextLocationProperty);
        set => SetValue(TextLocationProperty, value);
    }

    public static readonly DependencyProperty TextLocationProperty =
        DependencyProperty.Register(
            nameof(TextLocation),
            typeof(ImageTextLocation),
            typeof(ImageButton),
            new FrameworkPropertyMetadata((d, e) => ((ImageButton)d).SetDock((Dock)e.NewValue)));

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _contentPresenter = (ContentPresenter)GetTemplateChild("contentPresenter");
        SetDock((Dock)TextLocation);
    }

    public void SetDock(Dock newLocation)
    {
        if (_contentPresenter == null) return; //template not yet applied
        DockPanel.SetDock(_contentPresenter, newLocation);
    }
}

演示:

<Window x:Class="TestApp.MainWindow">
    <UniformGrid>
        <local:ImageButton TextLocation="Left"  />
        <local:ImageButton TextLocation="Right" />
        <local:ImageButton TextLocation="Top"   />
        <local:ImageButton TextLocation="Bottom"/>
    </UniformGrid>
</Window>