0
votes

I am working with WPF and looking for a best approach for creating re-usable expanders that are customized. Specifically the Expander header would remain the same, the content would vary.

The Expander header would have 6 buttons all wired to the same methods, which will be enforced via an interface.

Assume this is Expander 1

enter image description here

And this is another expander

enter image description here

The actual content will be text, buttons, whatever. It just has text for demo purposes.

The expander is meant to be used within a User Control, in which I have 44 of them, and don't want to repeat the code all over the place.

At the moment I am using the UserControls like the following in the Window XAML

xmlns:customcontrols="clr-namespace:MyNamespace.Controls;assembly=MyAssembly"

And the actual usage:

<customcontrols:FlexExtend ..... />

And inside each User Control I am using expander like this

<Expander Style="{StaticResource ModToolPanelStyle}"  Background="#403F3B"  Name="toolExpand" Header="{x:Static properties:Resources.AdductionAbduction_Label}" Collapsed="toolExpand_Collapsed" Expanded="toolExpand_Expanded">

....... all the inside body stuff

</expander>

Right now I'm looking at having to replicate the code 44 times, one for each expander in each of the 44 user controls that contain the an expander. Is there a way in WPF to make this a custom control that would have the buttons and everything? I'm think no since it wouldn't be able to be bound there for the on click?

UPDATE:

As suggested I created a DataTemplate in a seperate XAML.

<DataTemplate x:Key="DesignExpanderHeaderTemplate">
        <DockPanel>
            <TextBlock Name="ModName"
                       Foreground="White"
                       Text="Balls">
            </TextBlock>
            <Button Name="MoveUpButton"
                    Content="MoveUp"
                    Width="80"
                    Height="25">
            </Button>
        </DockPanel>
    </DataTemplate>

However now I am having issues binding the button from the user control:

var button = toolExpand.HeaderTemplate.FindName("MoveUpButton", toolExpand) as Button;
        button.Click += delegate (object sender, RoutedEventArgs args)
        {
            MessageBox.Show("The button has been pressed");
        };

The button is always null, so it is not able to find it.

This is how the XAML looks

<Expander Style="{StaticResource ModToolPanelStyle}"  
                  Background="#403F3B"  
                  x:Name="toolExpand" 
                  HeaderTemplate="{StaticResource DesignExpanderHeaderTemplate}" 
                  Collapsed="toolExpand_Collapsed" 
                  Expanded="toolExpand_Expanded">
1
Have you tried making a datatemplate? if you are working across multiple xaml files you can make a datatemplate once in your app.xaml as wellGordon Allocman
Can you provide more detail into how that would work? If I'm not missing something App.xaml would not be able to bind the OnClick event because it doesn't know about the methods since they live else where in the applicationgreyfox
You can move the expander xaml to a datatemplate and then have your controls reference that. If you are working accross multiple files you can move the datatemplate into a dictionary in your app.xaml. If you are binding each control to a different place you can put bind each control indiviudally. If the binding is the same per control just in different places, the compiler handles the binding assuming the path works outGordon Allocman
essentially you make sure the datatemplate is given a binding to an object that has all the properties you need it to have, then just set the expander's binding when you create an instance of oneGordon Allocman
@greyfox if the datacontext of your window/usercontrol etc.. has the command you want the button to bind to, you can do something like this in the button tag: Command="{Binding DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" where Window is whatever you are using, using the relative source you can bind in a datatemplate and it will work regardless of where the template gets usedGordon Allocman

1 Answers

0
votes

Following the guidance of user2455627 I was able to get this working. The main key was the following:

RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}

My datatemplate looks like the following.

<DataTemplate x:Key="DesignExpanderHeaderTemplate">
    <DockPanel>
        <TextBlock Foreground="White"
                   Text="{Binding ModName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
        </TextBlock>
        <Button Command="{Binding MoveUpCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
                Content="MoveUp"
                Width="80"
                Height="25">
        </Button>
        <Button Command="{Binding MoveDownCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
                Content="MoveUp"
                Width="80"
                Height="25">
        </Button>
        <Button Command="{Binding UndoCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
                Content="Undo"
                Width="80"
                Height="25"></Button>
    </DockPanel>
</DataTemplate>

Here is how I am using the data template in the XAML

<Expander Style="{StaticResource ModToolPanelStyle}"  
              Background="#403F3B"  
              x:Name="toolExpand" 
              HeaderTemplate="{StaticResource DesignExpanderHeaderTemplate}" 
              Collapsed="toolExpand_Collapsed" 
              Expanded="toolExpand_Expanded">

And here is the relevant code behind:

// Commands
    public RelayCommand MoveUpCommand { get; set; }
    public RelayCommand MoveDownCommand { get; set; }
    public RelayCommand UndoCommand { get; set; }
    public RelayCommand RedoCommand { get; set; }
    public RelayCommand ClearCommnand { get; set; }
    public RelayCommand RemoveCommand { get; set; }

    // Properties
    private string _modName;

// setup ModPanel
        ModName = "Something";
        MoveUpCommand = new RelayCommand(MoveUp);
        MoveDownCommand = new RelayCommand(MoveDown);
        UndoCommand = new RelayCommand(Undo);
        RedoCommand = new RelayCommand(Redo);
        ClearCommnand = new RelayCommand(Clear);
        RemoveCommand = new RelayCommand(Remove);

public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public string ModName
    {
        get { return _modName; }
        set
        {
            _modName = value;
            OnPropertyChanged();
        }
    }

    public void MoveUp(object obj)
    {
        throw new NotImplementedException();
    }

    public void MoveDown(object obj)
    {
        throw new NotImplementedException();
    }

    public void Undo(object obj)
    {
        throw new NotImplementedException();
    }

    public void Redo(object obj)
    {
        throw new NotImplementedException();
    }

    public void Remove(object obj)
    {
        throw new NotImplementedException();
    }

    public void Clear(object obj)
    {
        throw new NotImplementedException();
    }

    public void PreApply()
    {
        throw new NotImplementedException();
    }