I have my MainWindow which has an ItemsControl for my EngineersUserControl (Engineers_UC) along with other controls. My Engineers_UC consists of a few TextBoxes for which I want to add MouseBinding with the aim of being able to left click on a TextBox and another method in my ViewModel to be executed. I have read that the issue might be that the elements of ItemsControl are not focusable but I haven't found a solution. Any ideas ?
MainWindow:
<Grid>
<StackPanel>
<UserControl:Ribbon_UC Loaded="Ribbon_UC_Loaded" Margin="0,0,0,70"/>
<UserControl:Calendar_UC/>
<ItemsControl ItemsSource="{Binding Engineer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<UserControl:Engineers_UC />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
Engineers_UC:
<TextBox Name="EngineerName" IsReadOnly="True" Style="{StaticResource StyleTrigger} Text ="{Binding FULLNAME}">
<TextBox.InputBindings>
<MouseBinding Command="{Binding EngineerCommand}" CommandParameter="{Binding ElementName=EngineerName}" MouseAction="{Binding EngineerCommand.MouseGesture}"/>
</TextBox.InputBindings>
</TextBox>
EngineerCommand:
void RelayCommands()
{
EngineerCommand = new SimpleDelegateCommand(x => EngineerFunction(x))
{
MouseGesture = MouseAction.LeftClick
};
}
void EngineerFunction (object _engineername)
{
EngineerNameClicked = (_engineername as TextBox).Text;
}
public class SimpleDelegateCommand : ICommand
{
public Key GestureKey { get; set; }
public ModifierKeys GestureModifier { get; set; }
public MouseAction MouseGesture { get; set; }
Action<object> _executeDelegate;
public SimpleDelegateCommand(Action<object> executeDelegate)
{
_executeDelegate = executeDelegate;
}
public void Execute(object parameter)
{
_executeDelegate(parameter);
}
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
If the EngineerCommand command is defined in the same view model class as the collection to which you bind the ItemsSource property of the ItemsControl to (Engineer), you should use a RelativeSource for your binding(s) in the ItemTemplate:
<MouseBinding Command="{Binding DataContext.EngineerCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" ... />
Related
I'd like to trigger event for the elemens which is button in a listbox, the view is as below. It is a listbox with 32 buttons.The purpose is to toggle the button between 0-1 and trigger event for each element.
There are two solutions I am thinking about. The solution one is to set command for each button, it works but the problem is I can't catch a selectedItem or a selectedIndex successfully so that even thougn I know the button is toggled, but I don't know the item index.
<ListBox x:Name="lbDirection"
Grid.Row="1"
Grid.Column="1"
SelectionMode="Extended"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
Margin="30,26,44,83"
Style="{StaticResource ListBoxHorz}"
ItemContainerStyle="{StaticResource ItemNoBorder}"
SelectedItem="{Binding SelectedLineItem}"
ItemsSource="{Binding ButtonList}"
IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource ChangeButton}"
Command="{Binding DataContext.ToggleDirectionCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType= ListBox}}">
<Button.Content>
<TextBlock Text="{Binding BtnText}"
TextDecorations="Underline" />
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public class DigitalIOViewModel : BindableBase
{
public ICommand ToggleDirectionCommand { get; set; }
public ICommand ToggleStateCommand { get; set; }
private ObservableCollection<LineButtons> btnlist;
private LineButtons _selectedLineItem;
public LineButtons SelectedLineItem
{
get { return _selectedLineItem; }
set {
_selectedLineItem=value;
OnPropertyChanged("SelectedLineItem");
}
}
public DigitalIOViewModel()
{
btnlist = new ObservableCollection<LineButtons>();
CreateButtonList();
ToggleDirectionCommand = new RelayCommand(ToggleDirectionAction);
}
private void ToggleDirectionAction()
{
LineButtons selectedLineItem = SelectedLineItem;
int lineIndex=(SelectedLineItem!= null && ButtonList!=null)? ButtonList.IndexOf(SelectedLineItem) : -1;
if (selectedLineItem.BtnText == "1" && lineIndex == 0)
{
ButtonList[lineIndex] = new LineButtons() { BtnText = "0" };
}
else
ButtonList[lineIndex] = new LineButtons() { BtnText = "1" };
}
public ObservableCollection<LineButtons> ButtonList
{
get { return btnlist; }
set { btnlist = value; OnPropertyChanged("ButtonList"); }
}
public void CreateButtonList()
{
for (int i = 0; i < 32; i++)
{
ButtonList.Add(new LineButtons() { BtnText = "1"});
}
}
}
public class LineButtons : BindableBase
{
private string btnText;
public string BtnText
{
get { return btnText; }
set { btnText = value; OnPropertyChanged("BtnText"); }
}
}
I cannot get a correct selectedIndex from this code.
The second solution is to use the IsSelected/SelectedItem property instead of button command binding, I was wondering if there is conflict with the button command binding for item and IsSelected property fot listbox,so we can't use them at the same time, can someone help me out which is the best solution? Thanks in advance.
In addition to the Command property you can set the CommandParameter to the current LineButtons item (class names should be singular, names of collections use plural):
<ListBox.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource ChangeButton}"
Command="{Binding DataContext.ToggleDirectionCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType= ListBox}}"
CommandParameter="{Binding}">
<Button.Content>
<TextBlock Text="{Binding BtnText, UpdateSourceTrigger=PropertyChanged}"
TextDecorations="Underline" />
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
Then get the current item, the item that triggered the command, by accessing the command parameter:
private void Execute(object commandParameter)
{
var currentLineButton = commandParameter as LineButtons;
...
}
I have an object that consists of a string and an array. The string populates a ComboBox and the array populates a ListView depending on the selected string value. Each line of the ListViewconsists of a TextBlock and a CheckBox.
On submit I want to be able to verify which items have been selected for further processing but there's a disconnect when using the MVVM approach. I currently have the DataContext of the submit Button binding to the ListView but only the first value is being returned upon submit (somewhere I need to save the selected values to a list I assume but I'm not sure where). I added an IsSelected property to the model which I think is the first step, but after that I've been grasping at straws.
Model
namespace DataBinding_WPF.Model
{
public class ExampleModel { }
public class Example : INotifyPropertyChanged
{
private string _name;
private string[] _ids;
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected != value)
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public string[] IDs
{
get => _ids;
set
{
if (_ids != value)
{
_ids = value;
RaisePropertyChanged("IDs");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
}
}
ViewModel
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel : INotifyPropertyChanged
{
public ObservableCollection<Example> Examples
{
get;
set;
}
// SelectedItem in the ComboBox
// SelectedItem.Ids will be ItemsSource for the ListBox
private Example _selectedItem;
public Example SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
RaisePropertyChanged(nameof(SelectedItem));
}
}
// SelectedId in ListView
private string _selectedId;
public string SelectedId
{
get => _selectedId;
set
{
_selectedId = value;
RaisePropertyChanged(nameof(SelectedId));
}
}
private string _selectedCheckBox;
public string IsSelected
{
get => _selectedCheckBox;
set
{
_selectedCheckBox = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }, IsSelected = false });
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789", "101112" }, IsSelected = false });
Examples = examples;
}
/* BELOW IS A SNIPPET I ADDED FROM AN EXAMPLE I FOUND ONLINE BUT NOT SURE IF IT'S NEEDED */
private ObservableCollection<Example> _bindCheckBox;
public ObservableCollection<Example> BindingCheckBox
{
get => _bindCheckBox;
set
{
_bindCheckBox = value;
RaisePropertyChanged("BindingCheckBox");
}
}
}
}
View
<UserControl x:Class = "DataBinding_WPF.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:DataBinding_WPF"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding SelectedItem.IDs}"
DataContext="{Binding DataContext, ElementName=submit_btn}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left" Height="20" Width="100"
Click="Submit" x:Name="submit_btn">Submit</Button>
</StackPanel>
</Grid>
</UserControl>
View.cs
namespace DataBinding_WPF.Views
{
/// <summary>
/// Interaction logic for StudentView.xaml
/// </summary>
public partial class StudentView : UserControl
{
public StudentView()
{
InitializeComponent();
}
private void Submit(object sender, EventArgs e)
{
var selectedItems = ((Button)sender).DataContext;
// process each selected item
// foreach (var selected in ....) { }
}
}
}
The ListView control already exposes a selected items collection as property SelectedItems.
private void Submit(object sender, RoutedEventArgs e)
{
var selectedIds = myListView.SelectedItems.Cast<string>().ToList();
// ...do something with the items.
}
However, I doubt that you want to do this in the code-behind, but rather in the view model. For this purpose, WPF offers the concept of commands.
MVVM - Commands, RelayCommands and EventToCommand
What you need is a relay command or delegate command (the name varies across frameworks). It encapsulates a method that should be executed for e.g. a button click and a method to determine whether the command can be executed as an object that can be bound in the view. Unfortunately, WPF does not provide an implementation out-of-the-box, so you either have to copy an implementation like here or use an MVVM framework that already provides one, e.g. Microsoft MVVM Tookit.
You would expose a property Submit of type ICommand in your ExampleViewModel and initialize it in the constructor with an instance of RelayCommand<T> that delegates to a method to execute.
public class ExampleViewModel : INotifyPropertyChanged
{
public ExampleViewModel()
{
Submit = new RelayCommand<IList>(ExecuteSubmit);
}
public RelayCommand<IList> Submit { get; }
// ...other code.
private void ExecuteSubmit(IList selectedItems)
{
// ...do something with the items.
var selectedIds = selectedItems.Cast<string>().ToList();
return;
}
}
In your view, you would remove the Click event handler and bind the Submit property to the Command property of the Button. You can also bind the SelectedItems property of the ListView to the CommandParameter property, so the selected items are passed to the command on execution.
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
x:Name="submit_btn"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
Additionally, a few remarks about your XAML.
Names of controls in XAML should be Pascal-Case, starting with a capital letter.
You should remove the DataContext binding from ListView completely, as it automatically receives the same data context as the Button anyway.
DataContext="{Binding DataContext, ElementName=submit_btn}"
You can save yourself from exposing and binding the SelectedItem property in your ExampleViewModel, by using Master/Detail pattern for hierarchical data.
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name"/>
<ListView ItemsSource="{Binding Examples/IDs}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Name="myCheckBox"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}"
FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
</StackPanel>
</Grid>
If the view's data context is bound to the view then remove the DataContext from the ListView.
You could remove the item template and instead use a GridView like:
<ListView.View>
<GridView >
<GridViewColumn Header="Selected" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
Since the ItemSource is an Observable collection, there are several options to monitor changes in the checkboxes:
Add an event handler to the item changed event of the collection and then you can add the Name or the collection index to a local collection. e.g Examples[e.CollectionIndex].Name
Alternatively iterate over the observable collection and select those Examples where Selected = "true"
my question here is how to know which button is clicked. My buttons are bound to property of type ObservableCollection which contains objects of type Item and I need to use that object in my ViewModel when a button is clicked. Any ideas how to know which button is clicked? I had few ideas, like sending multiple Command Parameters (1.SelectedItems from ListBox 2.The Object from the button) or bind the object from the button to another property of type Item in the ViewModel after the button is clicked in order to use it. Any ideas will be apreciated.
I have this DataTemplate for buttons
<DataTemplate x:Key="ButtonTemplate">
<WrapPanel>
<Button x:Name="OrderButton"
FontSize="10"
Height="80" Width="80"
Content="{Binding Name}"
Command="{Binding OrderCommand,
Source={StaticResource OrderViewModel}}"
CommandParameter="{Binding ElementName=ListBoxUserControl, Path=SelectedItems}">
</Button>
</WrapPanel>
</DataTemplate>
My ViewModel
public class OrderViewModel : ObservableCollection<Order>, INotifyPropertyChanged
{
public CreateOrderCommand CreateOrderCommand { get; set; }
public ObservableCollection<Item> Data { get; set; }
public OrderViewModel()
{
this.CreateOrderCommand = new CreateOrderCommand(this);
DataObservableCollection data= new DataObservableCollection();
Data = data;
}
}
And I populate my buttons like this
<WrapPanel x:Name="OrderButtons">
<ItemsControl ItemTemplate="{StaticResource ButtonTemplate}"
ItemsSource="{Binding Data, Source={StaticResource OrderViewModel}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</WrapPanel>
Simply change the Button.CommandParameter binding to CommandParamter="{Binding}" if you want the data context of the button (i.e. the item from your items source) as the command parameter or,
CommandParameter="{Binding RelativeSource={RelativeSource Self}}" if you want the actual button that was clicked.
First send the Button DataContext using the CommandParameter. To send the SelectedItem of your Listbox you can use
<Listbox SelectedItem="{Binding SelectedItem}"/>
in your Listbox and make a SelectedItem property in your ViewModel.
private YourItemObject mySelectedItem;
public YourItemObject SelectedItem
{
get { return mySelectedItem; }
set
{
value = mySelectedItem
}
Now you can use the SelectedItem in your ViewModel when the Button gets clicket. If you have multiple selections it gets a little bit more tricky ;).
private ButtonClicked(Parameter object)
{
SelectedItem.UsingIt();
if(object is YourButtonDataContext){
YourButtonDataContext.UsingIt();
}
}
Update with MultiSelection:
With Multiselection you have to do your own Listbox.
public class CustomListBox : ListBox
{
public CustomListBox()
{
this.SelectionChanged += CustomListBox_SelectionChanged;
}
void CustomListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList = this.SelectedItems;
}
#region SelectedItemsList
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(CustomListBox), new PropertyMetadata(null));
#endregion
}
In the ViewModel you have to have a property with the SelectedItems.
private IList mySelectedData = new List<SelectedDataObject>();
public IList SelectedData
{
get { return mySelectedData ; }
set
{
if (mySelectedData != value)
{
mySelectedData = value;
RaisePropertyChanged(() => SelectedData);
}
}
}
The XAML Looks like this:
<local:CustomListBox ItemsSource="{Binding YourList}" SelectionMode="Extended" SelectedItemsList="{Binding SelectedData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
...
</local:CustomListBox>
Source for Multiselection in DataGrid is: https://stackoverflow.com/a/22908694/3330227
I'm trying to check all the checkboxes through a binding..
The getChecked property does get changed to true after clicking the button but the checkboxes are just not getting checked. Does someone see what I'm doing wrong here? This is the XAML code for the listbox.
<ListBox Name="scroll" ItemContainerStyle ="{StaticResource _ListBoxItemStyle}" Tag="{Binding SortingIndex}" BorderBrush="#C62828" BorderThickness="1" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="checkboxStack">
<CheckBox IsChecked="{Binding Path=getChecked}" Content="{Binding Path=vraag}" Style="{StaticResource LifesaversCheckBoxesA}"/>
<StackPanel Margin="20,0,0,0">
<RadioButton GroupName="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}, Path=Tag}" Content="{Binding Path=antwoorden[0]}" FontSize="15" />
<RadioButton GroupName="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}, Path=Tag}" Content="{Binding Path=antwoorden[1]}" FontSize="15" />
<RadioButton GroupName="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}, Path=Tag}" Content="{Binding Path=antwoorden[2]}" FontSize="15" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is the event handler for the button I made to change the getChecked boolean to true for each vraag in vragenLijst. The sample data is just to generate some random strings.
public partial class LivesaversWindow : UserControl
{
ObservableCollection<Vraag> vragenLijst;
public LivesaversWindow()
{
InitializeComponent();
vragenLijst = new VragenList(SampleData.vragen());
scroll.ItemsSource = vragenLijst;
}
private void alles_Selecteren(object sender, RoutedEventArgs e)
{
if ((string)select.Content == "Alles selecteren")
{
foreach(Vraag vraag in vragenLijst)
{
vraag.getChecked = true;
}
select.Content = "Alles deselecteren";
}
else
{
foreach (Vraag vraag in vragenLijst)
{
vraag.getChecked = false;
}
select.Content = "Alles selecteren";
}
}
And these are the 2 classes I'm using.
public class Vraag
{
public List<string> antwoorden { get; set; }
public string vraag { get; set; }
public Boolean getChecked { get; set; }
}
public class VragenList : ObservableCollection<Vraag>
{
public VragenList(List<Vraag> vragen) :base()
{
foreach (var vraag in vragen)
{
Add(vraag);
}
}
}
Your class Vraag is not implementing the INotifyPropertyChanged Interface. Therefore the UI is not registering any changes that are made to your objects displaying in the view:
So bascially what you need to do is implementing the INotifyPropertyChanged-Interface kind of like this:
public class Vraag : INotifyPropertyChanged
{
public List<string> antwoorden { get; set; }
public string vraag { get; set; }
private bool isChecked;
public bool getChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
Hope this helps!
Your class Vraag must implement INotifyPropertyChanged interface
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
And this is another expander
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">
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();
}