Assign Click Event to Button in WPF ResourceDictionary - c#

I keep my theme file (ResourceDictionary) in a separate project. I'm using the following ControlTemplate structure for the DataGrid in my ResourceDictionary:
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" Margin="10 5" />
<Button x:Name="btnFilter" Content="" FontFamily="{StaticResource FontAwesome}" FontSize="16" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Where I use DataGrid in the related project, I need to assign a click event to the button named btnFilter above, how can I do this?
Methods like VisualTree do not work very well. Since I have nearly 20 columns in Datagrid, I use horizontal scroll and VisualTree does not see the columns hidden by scroll.
What are the best practices I should follow here?

As Button.Click is a bubbling routed event, you can listen it at the control nearer to the root.
For example, assuming you name the Style as "HeaderStyle", it would be like the following.
<DataGrid ColumnHeaderStyle="{StaticResource HeaderStyle}"
Button.Click="Button_Click">
<DataGrid.Columns>
<DataGridTextColumn Header="HeaderText"/>
</DataGrid.Columns>
</DataGrid>
In case there are multiple Buttons in that control, you can filter by checking RoutedEventArgs.OriginalSource.
private void Button_Click(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is Button { Name: "btnFilter" })
{
// Handle event here.
}
}

Related

WPF MVVM: Set focus to current selected item in Datagrid on button click

I have a MVVM WPF app in Visual Studio 2008 and NET Framework 3.5 SP1. Among other controls, this app have a datagrid and a button.
Button:
<Button Grid.Column="1" Command="{Binding CalculateCommand}" FocusManager.FocusedElement="{Binding ElementName=myDataGrid}" HorizontalAlignment="Right">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myDataGrid, Path=SelectedItem}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Image VerticalAlignment="Center" Source="/MyWPFApp;component/Images/Calculate.png"></Image>
<TextBlock VerticalAlignment="Center">Calculate</TextBlock>
</StackPanel>
</Button>
What I am trying to do is to set focus on the current selected item in the datagrid once button is clicked so when I click on up/down arrow key in the keyboard I can move to any other item in the datagrid.
So I have tried to set FocusManager.FocusedElement property in the button but it is not working. I have also tried to set as is without specifying the Path:
FocusManager.FocusedElement="{Binding ElementName=myDataGrid}"
In the first attempt, without setting the Path in the property:
FocusManager.FocusedElement="{Binding ElementName=myDataGrid}"
when I click on down of up arrow key in the keyboard (after clicking button), it change the focus to another control in the UI that is not the current selected item in the datagrid.
In the second attempt, setting the Path in the property:
FocusManager.FocusedElement="{Binding ElementName=myDataGrid, Path=SelectedItem}"
it simply does nothing, neither no focus in the current selected item in the datagrid nor in any other control.
Also I have tried an attached behaviour as said here but it is not working:
<Button Grid.Column="1" Command="{Binding CalculateCommand}" classes:EventFocusAttachment.ElementToFocus="{Binding ElementName=myDataGrid}" HorizontalAlignment="Right">
Another attempt:
It works on second key click, first click is ignored.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
if (myDataGrid.SelectedIndex > -1)
{
var selectedRow = (Microsoft.Windows.Controls.DataGridRow)myDataGrid.ItemContainerGenerator.ContainerFromIndex(myDataGrid.SelectedIndex);
FocusManager.SetIsFocusScope(selectedRow, true);
FocusManager.SetFocusedElement(selectedRow, selectedRow);
}
}
#Evk Solution works perfectly.
The following should work:
if (myDataGrid.SelectedIndex > -1) {
var container = (DataGridRow) myDataGrid.ItemContainerGenerator.ContainerFromIndex(myDataGrid.SelectedIndex);
if (container != null) {
container.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
You can put that code into Button.Click event (nothing wrong here, what we are doing is completely view-only thing) or if you don't like code-behind you can create attached property\behavior from that.

Closable tab with tab item being a user control

I am working on a project in C# WPF. I have a tab container and I want to dynamically load different types of tabs into the tab container as the user requires. As an example I am doing something like the following:
tabContainer.Items.Add(new MyUserControl());
I want each tab to have a close button so the tab can be removed the container when the user no longer requires it.
I found this code project example but from what I can see you are a loading a user control which contains the xaml for the tab itself, not the tab content or am I missing something.
How can I load in my User Control into the tab container, but also have the tab closable.
Currently the tab that I am loading in uses some static binding to set the tab title using the following:
<TabControl x:Name="tabContainer" Grid.Column="2" Margin="10,45,0,0" RenderTransformOrigin="0.5,0.55" Grid.ColumnSpan="3">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
</TabControl>
My user control then has a `public string TabHeader{get;set;} which gets set in the constructor depending on what constructor of my user control is used.
You will have to define the close Button yourself. You could for example do this in the HeaderTemplate of the TabItem:
<TabControl x:Name="tabContainer">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button Content="x" Click="Button_Click_2"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TabItem}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The Tag property is bound to the UserControl in the Items collection which you can remove in the click event handler of the Button, like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
tabContainer.Items.Add(new MyUserControl());
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
tabContainer.Items.Remove(button.Tag);
}
}
If you want to add a close button to each tab, that would be in the TabItem style ControlTemplate. Normally you'd specify the data context (i.e. the data only that's driving the content) in Content and then specify the look in ContentTemplate. If your Content is a UserControl then you don't specify the ContentTemplate since a UserControl knows how to draw itself.
For my sins, I've added close-tab buttons to the WPF TabControl. I ended up putting the close button in the ItemTemplate. Here's a minimal version that works with the way you're populating the TabControl and the header content:
<TabControl
>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Grid.Column="0"
/>
<Button
VerticalAlignment="Center"
Grid.Column="1">
<Path
Data="M 0, 0 L 12, 12 M 12,0 L 0,12"
Stroke="Red"
StrokeThickness="2"
Width="12"
Height="12"
/>
</Button>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<local:UserControl1 TabHeader="First Item" />
<local:UserControl1 TabHeader="Second Item" />
</TabControl>

change TextBox readonly by clicking on ListBoxItem

I have a TextBox inside a ListBoxItem which is disabled so I can drag and drop it in the ListBox.
Once I double click it I want it to be Enabled so I can edit the Text and when I'm done I want it do be Disabled again to do drag and drop.
I have the MouseDoubleClick event on the ListBoxItem but it doesn't change the TextBox ReadOnly. Can anybody tell me how to achieve this.
at the moment TextBox returns null. seems like I don't get access to it the way I'm trying.
XAML
<ListBox Name="Locations" Cursor="Hand" HorizontalAlignment="Left" Height="351" Margin="10,48,0,0" VerticalAlignment="Top" Width="285" ItemsSource="{Binding Locations}" dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Name="textBox" Text="{Binding Path=Name}" IsHitTestVisible="False" Width="270" Background="Transparent" BorderThickness="0" Margin="2"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In View
private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
ListBoxItem item = sender as ListBoxItem;
textBox.IsReadOnly = false;
}
That's because the textbox "doesn't exists" in the visual tree, WPF creates one for each listitem when needed, so you never have a reference to it. It is possible to navigate the visual tree and get the textbox reference but I advise against it, instead, create a property in your item model, something like "EditMode" that when you set it to true, the textbox will enable through the binding of the IsEnabled property.

C# WPF Handling double click events from multiple, dynamically generate datagrids through a single handler

In my current implementation, I spawn tabs and grids dynamically.
Basically, a new grid needs to be created by a double click on a any row of a previous grid and use the row data for other provessing.
this.AddHandler(DataGrid.MouseDoubleClickEvent, new RoutedEventHandler (Generic_DoubleClick));
This handles for any double click even outside the grid and not specifically for the grid.
I need to find a handler which can return the row values specifically to that grid. Please suggest a workaround or a easier way of doing this.
Thanks.
Handle the double click routed event from datagrid row of the datagrid.
<tk:DataGrid>
<tk:DataGrid.Resources>
<Style TargetType="{x:Type tk:DataGridRow}">
<EventSetter Event="MouseDoubleClick"
Handler="DataGridRow_MouseDoubleClick"/>
</Style>
</tk:DataGrid.Resources>
<tk:DataGrid.ItemsSource>
<x:Array Type="{x:Type TextBlock}">
<TextBlock Text="1" Tag="1.1"/>
<TextBlock Text="2" Tag="1.2"/>
<TextBlock Text="3" Tag="1.3"/>
<TextBlock Text="4" Tag="1.4"/>
</x:Array>
</tk:DataGrid.ItemsSource>
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Text" Binding="{Binding Text}"/>
<tk:DataGridTextColumn Header="Tag" Binding="{Binding Tag}"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
In code behind
private void DataGridRow_MouseDoubleClick(
object sender, MouseButtonEventArgs e)
{
var dgRow = sender as Microsoft.Windows.Controls.DataGridRow;
var cellContentElement = e.OriginalSource as UIElement;
}
Bonus is cellContentElement is the content element of the cell that was double clicked on the row ... e.g. in case of DataGridTextColumn it will be TextBlock in the cell.

C# - how do I prevent mousewheel-scrolling in my combobox?

I have a combobox and I want to prevent the user from scrolling through the items with the mousewheel.
Is there an easy way to do that?
(C#, VS2008)
Use the MouseWheel event for your ComboBox:
void comboBox1_MouseWheel(object sender, MouseEventArgs e) {
((HandledMouseEventArgs)e).Handled = true;
}
Note: you'll have to create event in code:
comboBox1.MouseWheel += new MouseEventHandler(comboBox1_MouseWheel);
For WPF, handle the PreviewMouseWheel event instead.
It would also be a good idea to consider ComboBox.IsDropDownOpen so the user can still use mouse scroll if there are a lot of items in the selection when the ComboBox is expanded.
Another thing is to apply the same behavior across the whole application.
I usually do all the above using the following code:
App.xaml
<Application.Resources>
<Style TargetType="ComboBox">
<EventSetter Event="PreviewMouseWheel" Handler="ComboBox_PreviewMouseWheel" />
</Style>
</Application.Resources>
App.xaml.cs
private void ComboBox_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
e.Handled = !((System.Windows.Controls.ComboBox)sender).IsDropDownOpen;
}
I use another solution that also works on Mono.
Goal is to prevent accidentally scrolling (that is when the user is not looking at the comboBox when using the mouse wheel). If he / she scroll outside the visible portion of comboBox , the combo box should not scroll, otherwise it should.
My solution:
Place a read only text box outside the visible portion of the screen. In form_load I placed the line: hiddenTextbox.left = -100 ;
Set the focus to this text box when the mouse leaves the combo box using mouse leave event. In comboBox1_MouseLeave I placed the line: hiddenTextbox.focus();
Handle mouseWheel event: From1.MouseWheel += Form1_MouseWheel;
textBoxHidden.MouseWheel += Form1_MouseWheel;
My Combobox's were placed inside a DataGrid [C#, WPF XAML] just like this:
<DataGrid x:Name="dgvFieldsMapping" Grid.Row="1" ItemsSource="{Binding}">
<DataGrid.Columns>
...
<DataGridTemplateColumn Width="*" Header="Destination Field" >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<ComboBox ItemsSource="{Binding Source={StaticResource CustomerDbFields}}" SelectedValue="{Binding destinationField, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...
</DataGrid.Columns>
</DataGrid>
So whenever a DropDown was closed after selecting a Value, the Mousewheel would scroll that Combobox's Items and modify my Selection.
I ended up modifying my XAML to look like this:
<DataGrid x:Name="dgvFieldsMapping" Grid.Row="1" ItemsSource="{Binding}">
<DataGrid.Resources>
<Style x:Key="dgvComboBox_Loaded" TargetType="ComboBox">
<EventSetter Event="Loaded" Handler="dgvCombobox_Loaded" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
...
<DataGridTemplateColumn Width="*" Header="Destination Field" >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<ComboBox Style="{StaticResource dgvComboBox_Loaded}" ItemsSource="{Binding Source={StaticResource CustomerDbFields}}" SelectedValue="{Binding destinationField, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...
</DataGrid.Columns>
</DataGrid>
And adding these lines in codebehind
public void dgvCombobox_Loaded(Object sender, RoutedEventArgs e)
{
((ComboBox)sender).DropDownClosed -= ComboBox_OnDropDownClosed;
((ComboBox)sender).DropDownClosed += new System.EventHandler(ComboBox_OnDropDownClosed);
}
void ComboBox_OnDropDownClosed(object sender, System.EventArgs e)
{
dgvFieldsMapping.Focus();
}
In this way I just move the Focus away from the ComboBox to the outer DataGrid after closing its corresponding DropDown, so I don't need to add any dummy FrameWorkElement

Categories