Search code examples
c#wpfxamliconspathgeometry

In a C# Wpf application, how do you change a button's icon in a usercontrol dynamically


So in my application I have a usercontrol. It has among other things a button with an image defined as gemoetry data:

    <!--  Add new Item Button  -->
    <Button
        x:Name="createButton"
        Width="40"
        Height="40"
        Margin="0,0,20,0"
        HorizontalAlignment="Right"
        VerticalAlignment="Center"
        Command="{Binding OpenNewWindowCommand}"
        Style="{StaticResource RoundAccentButton}">
        <!--  Icon  -->
        <Viewbox
            x:Name="createViewbox"
            Width="22"
            Height="22">
            <Path Data="{StaticResource NewTaskIcon}" Fill="{StaticResource OnAccent}" />
        </Viewbox>
    </Button>

..and the Geometry data, which is placed in a ResourceDictionary, looks like this:

<!--  New Task Icon  -->
<PathGeometry x:Key="NewTaskIcon" Figures="M17 21.2188V17.4688H13.25V14.9688H17V11.2188H19.5V14.9688H23.25V17.4688H19.5V21.2188H17ZM0.75 17.5V15H3.25V17.5H0.75ZM5.75 17.5V15H10.8438C10.7812 15.4375 10.7552 15.8542 10.7656 16.25C10.776 16.6458 10.8125 17.0625 10.875 17.5H5.75ZM0.75 12.5V10H3.25V12.5H0.75ZM5.75 12.5V10H14.0625C13.5833 10.3333 13.151 10.7083 12.7656 11.125C12.3802 11.5417 12.0417 12 11.75 12.5H5.75ZM0.75 7.5V5H3.25V7.5H0.75ZM5.75 7.5V5H20.75V7.5H5.75ZM0.75 2.5V0H3.25V2.5H0.75ZM5.75 2.5V0H20.75V2.5H5.75Z" />

The problem is that I of course do not want to set the PathData as a hardcoded value in the usercontrol, but instead set it where the usercontrol is used. For instance, I use this usercontrol, called SearchItems, in another View called TaskView.xaml like this:

<UserControl
x:Class="myTaskManager.UI.Wpf.MVVM.Views.TaskView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:myTaskManager.UI.Wpf.MVVM.UserControls"
xmlns:vm="clr-namespace:myTaskManager.UI.Wpf.MVVM.ViewModels"
d:DesignHeight="450"
d:DesignWidth="435"
mc:Ignorable="d">
<UserControl.DataContext>
    <vm:TaskViewViewModel />
</UserControl.DataContext>
<DockPanel>
    <Border Width="435" Background="{StaticResource TaskListBackground}">

        <uc:SearchItems />

    </Border>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="140" />
        </Grid.ColumnDefinitions>

        <!--  Task Viewer  -->
        <Grid Grid.Column="0">
            <Grid.RowDefinitions>

. . .

What I would like to do is to access the Viewbox with the PathData to change the icon depending on where the usercontrol is used. I thought I could do that by defining a DependencyProperty, let's say we call it PathData, and then set it in the Views where I use the usercontrol, i.e. something like this (below is a view defined as a usercontrol which uses the SearchItems usercontrol):

<UserControl
x:Class="myTaskManager.UI.Wpf.MVVM.Views.TaskView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:myTaskManager.UI.Wpf.MVVM.UserControls"
xmlns:vm="clr-namespace:myTaskManager.UI.Wpf.MVVM.ViewModels"
d:DesignHeight="450"
d:DesignWidth="435"
mc:Ignorable="d">
<UserControl.DataContext>
    <vm:TaskViewViewModel />
</UserControl.DataContext>
<DockPanel>
    <Border Width="435" Background="{StaticResource TaskListBackground}">

        <uc:SearchItems PathData="{StaticResource NewTaskIcon}" />

    </Border>

..or by simply defining a property in the code-behind for the usercontrol called PathData:

        public Geometry PathData { get; set; }

and then in the usercontrol's xaml code have it done like this:

        <!--  Add new Item Button  -->
    <Button
        x:Name="createButton"
        Width="40"
        Height="40"
        Margin="0,0,20,0"
        HorizontalAlignment="Right"
        VerticalAlignment="Center"
        Command="{Binding OpenNewWindowCommand}"
        Style="{StaticResource RoundAccentButton}">
        <!--  Icon  -->
        <Viewbox
            x:Name="createViewbox"
            Width="22"
            Height="22">
            <Path Data="{Binding PathData}" Fill="{StaticResource OnAccent}" />
        </Viewbox>
    </Button>

..and then in the Views that use the usercontrol just use the same syntax as above, i.e.:

<UserControl
x:Class="myTaskManager.UI.Wpf.MVVM.Views.TaskView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:myTaskManager.UI.Wpf.MVVM.UserControls"
xmlns:vm="clr-namespace:myTaskManager.UI.Wpf.MVVM.ViewModels"
d:DesignHeight="450"
d:DesignWidth="435"
mc:Ignorable="d">
<UserControl.DataContext>
    <vm:TaskViewViewModel />
</UserControl.DataContext>
<DockPanel>
    <Border Width="435" Background="{StaticResource TaskListBackground}">

        <uc:SearchItems PathData="{StaticResource NewTaskIcon}" />

    </Border>

..where I set the PathData dynamically, but neither of these ways seem to work. What would be the best way to accomplish this?

Any answer pointing me in the right direction would be highly appreciated.

Thanks, Peter


Solution

  • The UserControl should expose a dependency property in order to make it bindable and settable by a Style.

    public static readonly DependencyProperty IconDataProperty =
        DependencyProperty.Register(
            nameof(IconData), typeof(Geometry), typeof(SearchItems));
    
    public Geometry IconData
    {
        get => (Geometry)GetValue(IconDataProperty);
        set => SetValue(IconDataProperty, value);
    }
    

    It would be used in a Path element in the control's XAML via a RelativeSource Binding. Note that a Viewbox is not required.

    <Button Width="40" Height="40" ...>
        <Path Width="22" Height="22"
              Data="{Binding IconData,
                     RelativeSource={RelativeSource AncestorType=UserControl}}"
              Fill="{StaticResource OnAccent}"
              Stretch="Uniform"/>
    </Button>
    

    Now you could assign an icon like

    <uc:SearchItems IconData="{StaticResource NewTaskIcon}" />