Home > Software design >  Setting DockPanel.Dock according to DependencyProperty of a custom control does not work
Setting DockPanel.Dock according to DependencyProperty of a custom control does not work

Time:04-11

I have an enum:

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

And a custom UserControl that contains dependency property of that enum.

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

I have added created a converter that converts from ImageTextLocation to 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();
    }
}

And added an instance of the converter to the resources of my UserControl:

<coverter:EnumToDockConverter x:Key="EnumToDockConvert"/>

Then, I try to set the DockPanel.Dock of the custom control using TextLocation="{Binding ImageTextPosition}", where ImageTextPosition is the string property of my view model bound to the UserControl.

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

DockPanel's Dock does not set when I set the dependency property.

CodePudding user response:

I don't see any need to use a converter. In your property changed callback, call DockPanel.SetDock() directly to change the dock position of the content presenter. I would also define ImageTextLocation in terms of Dock so that you can cast it.

UPDATE: OP clarified that ImageButtonControl overrides the default ControlTemplate of the Button, so I have updated my answer to work in that scenario. Derive directly from Button, instead of UserControl, so the contents of the template are accessible without jumping through hoops.

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>

Code Behind

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

Demo:

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

enter image description here

  • Related