1

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.

Timothy G.
  • 6,335
  • 7
  • 30
  • 46
Rashedul.Rubel
  • 3,446
  • 25
  • 36
  • DockPanel.Dock is an attached property, you can't bind it this way. Instead, call GetDock and SetDock when your TextLocation property changes and you can convert from your enum to the dock layout there. Or use the attached property syntax described here: https://stackoverflow.com/questions/5832208/wpf-attached-property-data-binding – John V Apr 09 '22 at 01:27
  • Thanks @John V, Can you give an example of code? – Rashedul.Rubel Apr 09 '22 at 01:32
  • 1
    Note that your PropertyChangedCallback is redundant. It makes no sense to call `imageButton.SetCurrentValue` with the value that the property already has. – Clemens Apr 09 '22 at 06:51

1 Answers1

1

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

John V
  • 1,286
  • 9
  • 15
  • Thanks, My ContentPresenter is inside a button template. So I cannot get the presenter like this imageButton.contentPresenter. I have edited my question – Rashedul.Rubel Apr 09 '22 at 01:57
  • The purpose to add contentpresenter inside button template is to set text of a button with an image. And dockpanel is needed for contentpresenter so that text of button can be docked dynamically – Rashedul.Rubel Apr 09 '22 at 06:53
  • Thanks it works, please change to because in your case the class name is ImageButton – Rashedul.Rubel Apr 11 '22 at 06:29
  • Okay, but delete all these comments. – John V Apr 11 '22 at 06:38