How to select ListBoxItem upon clicking on button in Template? - wpf

I have the following Data Template applied to a ListBox:
<DataTemplate x:Key="MyTemplate" DataType="{x:Type DAL:Person}">
<StackPanel Orientation="Horizontal">
<Button Content="X" Command="{x:Static cmd:MyCommands.Remove}"/>
<TextBlock Text="{Binding Person.FullName}" />
</StackPanel>
</DataTemplate>
When I click on the button the command gets fired but the ListBoxItem doesn't get selected. How do I force it to get selected, so that I can get the selected item in my "executed" method?
Thanks

A better way, since you're not really interested in selecting the item (because it will quickly get deleted anyway) would be to pass the item itself to the Command as a CommandParameter.
Alternatively, you can go about in a roundabout manner either with code-behind or with triggers, but I don't think it would be as to the point. For example:
you could handle the ButtonBase.Click event on your listbox, like
<ListBox ButtonBase.Click="lb_Click"
...
then in your code behind, do this:
private void lb_Click(object sender, RoutedEventArgs e)
{
object clicked = (e.OriginalSource as FrameworkElement).DataContext;
var lbi = lb.ItemContainerGenerator.ContainerFromItem(clicked) as ListBoxItem;
lbi.IsSelected = true;
}
That gets the clicked bound item, because the datacontext of the button is inherited from it's templated item, then the actual autogenerated ListBoxItem from the ListBox's ItemContainerGenerator, and sets the IsSelected property to true. I think that's one of the fastest and easiest ways. Also works with multiple ButtonBase-derived objects in the template.
Of course you can also more nicely encapsulate all this (more or less exactly the same) as a reusable Behavior:
public class SelectItemOnButtonClick : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(handler), true);
}
protected override void OnDetaching()
{
this.AssociatedObject.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(handler));
base.OnDetaching();
}
private void handler(object s, RoutedEventArgs e)
{
object clicked = (e.OriginalSource as FrameworkElement).DataContext;
var lbi = AssociatedObject.ItemContainerGenerator.ContainerFromItem(clicked) as ListBoxItem;
lbi.IsSelected = true;
}
}
You can use it like this:
<ListBox xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" ...>
<i:Interaction.Behaviors>
<local:SelectItemOnButtonClick />
</i:Interaction.Behaviors>
</ListBox>
Add error handling code like at least null checks, of course - wouldn't want a simple thing like this bombing your app.
To understand the problem, the button sets the Handled property to true for all the mouse events that act on it (MouseDown/Click) so they aren't being considered by the ListBoxItem. You could also attach the MouseDown event to the ListBox and walk the visual tree upwards until you reach the parent ListBoxItem but that's a lot more tricky... eh if you're curious, you can read this article to know why, basically you'll also encounter FrameworkContentElements (which also respond to MouseDown) so the code will get more complicated, with the upside that anything clicked inside the datatemplate will trigger the ListBoxItem to be selected, regardless of whether it marked the event as handled.
Heh, I also tried to do it exclusively with styles and triggers but it got ugly fast and I lost interest (and lost track of all the... err thingies). Basically it could be solved, I think, but I reaaaly don't think it's worth the bother. Maybe I overlooked something obvious though, don't know.

Make the underlying object expose a RemoveCommand property, and bind the button's Command property to it. This simplifies the data template; it also greatly simplifies the case where application logic may dictate that a specific item can't be removed.

Alex, thanks for answer. Your solution with Behavior is great. First solution is not so good because that will work only if you click on specific Button. Here is one more solution that will work on click on arbitrary control form ListBoxItem template:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem"
BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
That is XAML only approach. I also set BasedOn property just to be sure to not override the current ListBoxItem style.

Related

Making a double click event work only when clicked on specific datagrid row?

I currently have a datagrid that I am binding to a method within my xaml's .cs file. The goal is to be able to double click a given row, and it does a task. However, I do not want the user to be able to call the function by clicking a row, then double click wherever else on the datagrid. I would like the user to only be able to call the function when double clicking on a given row.
Thanks!
You haven't really provided enough info to answer this question. Are you wanting to use a event handler in the code-behind? Or an ICommand handler in the item itself? The latter is what you should be using, but since you haven't tagged MVVM I'll assume the former.
The problem is you need to set an EventSetter on the DataRow style, but EventSetters can't be set with DataTriggers, so you need to set it on all of them and do your filtering in the handler (there are ways around this, but it starts getting convoluted at which point you're better off just switching to MVVM). Declare your DataGrid and set the event handler via the RowStyle:
<DataGrid ItemsSource="{Binding MyItems}" AutoGeneratingColumn="OnAutoGeneratingColumn">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseDoubleClick" Handler="DataGridRow_MouseDoubleClick" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
Then in the handler do your check:
private void DataGridRow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
var dataElement = row?.DataContext as MyItem;
if (dataElement?.CanDoubleClick != true)
return;
// process double-click here
return;
}

How do i handle cell double click event on WPF DataGrid, equivalent to windows DataGrid's Events?

As you know, in windows C#'s gridview, if we want to handle a click/double click event on cell then there are events like CellClick, CellDoubleClick, etc.
So, i wanna do same like as windows gridview with WPF DataGrid. I have searched so far but neither answer is applicable nor useful. Some of them says use the MouseDoubleClick event but, in this event, we have to check for each row as well as item in that row, so it is time consuming to check every cell for data and timing is most important here.
My DataGrid is bounded to DataTable and AutoGeneratedColumn is False. If your answer is based on AutoGeneratedColumn=True then it is not possible. Even, i 'm changing the styles of datagrid cell according to data, so there is no way to change AutoGeneratedColumn property.
A Cell Clicking/Double Clicking event should be as faster as windows grid's event. If it is possible then tell me how, and if not, then what is the alternative to do it?
Please Help Me.....
Thanks a lot....
I know this may be a little late to the party, but this might be useful to someone else down the road.
In your MyView.xaml:
<DataGrid x:Name="MyDataGrid" ...>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="MouseDoubleClick" Handler="DataGridCell_MouseDoubleClick"/>
</Style>
</DataGrid.Resources>
<!-- TODO: The rest of your DataGrid -->
</DataGrid>
In your MyView.xaml.cs:
private void DataGridCell_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var dataGridCellTarget = (DataGridCell)sender;
// TODO: Your logic here
}
An alternative way would to be define a DataGridTemplateColumn instead of using the predefined columns like DataGridCheckBoxColumn, DataGridComboBoxColumn and then add an event handler to the UI element defined in the data template.
Below I have defined a MouseDown event handler for a TextBlock Cell.
<DataGrid AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock MouseDown="TextBlock_MouseDown"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
In the Code behind file:
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
TextBlock block = sender as TextBlock;
if (block != null)
{
// Some Logic
// block.Text
}
}
I know coding WPF is sometimes a PITA. Here you would have to handle the MouseDoubleClick event anyway. Then search the source object hierarchy to find a DataGridRow and do whatever with it.
UPDATE: Sample code
XAML
<dg:DataGrid MouseDoubleClick="OnDoubleClick" />
Code behind
private void OnDoubleClick(object sender, MouseButtonEventArgs e)
{
DependencyObject source = (DependencyObject) e.OriginalSource;
var row = GetDataGridRowObject(source);
if (row == null)
{
return;
}
else
{
// Do whatever with it
}
e.Handled = true;
}
private DataGridRow GetDataGridRowObject(DependencyObject source)
{
// Write your own code to recursively traverse up via the source
// until you find a DataGridRow object. Otherwise return null.
}
}
I have used something like this:
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ShowOverlay}" CommandParameter="{Binding Parameter}" />
</DataGrid.InputBindings>
And handle my commands in my View Model.

In WPF, how do I find an element in a template that's switched in via a trigger?

I have a UserControl (not a lookless custom control) which, depending on some custom state properties, swaps in various ContentTemplates, all defined as resources in the associated XAML file. In the code-behind, I need to find one of the elements in the swapped-in ContentTemplates.
Now in a lookless control (i.e. a custom control), you simply override OnApplyTemplate then use FindName, but that override doesn't fire when the ContentTemplate gets switched by a trigger (...at least not for a UserControl. I haven't tested that functionality with a custom control.)
Now I've tried wiring up the Loaded event to the control in the swapped-in template, which does fire in the code-behind, then I simply store 'sender' in a class-level variable. However, when I try to clear that value by subscribing to the Unloaded event, that doesn't fire either because the tempalte gets swapped out, thus unwiring that event before it has a chance to be called and the control unloads from the screen silently, but I still have that hung reference in the code-behind.
To simulate the OnApplyTemplate functionality, I'm considering subscribing to the ContentTemplateChanged notification and just using VisualTreeHelper to look for the control I want, but I'm wondering if there's a better way, hence this post.
Any ideas?
For reference, here's a very-stripped-down example of the control I have. In this example, if IsEditing is true, I want to find the textbox named 'FindMe'. If IsEditing is false which means the ContentTemplate isn't swapped in, I want to get 'null'...
<UserControl x:Class="Crestron.Tools.ProgramDesigner.Controls.EditableTextBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Crestron.Tools.ProgramDesigner.Controls"
x:Name="Root">
<UserControl.Resources>
<DataTemplate x:Key="EditModeTemplate">
<TextBox x:Name="FindMe"
Text="{Binding Text, ElementName=Root}" />
</DataTemplate>
<Style TargetType="{x:Type local:EditableTextBlock}">
<Style.Triggers>
<Trigger Property="IsEditing" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<TextBlock x:Name="TextBlock"
Text="{Binding Text, ElementName=Root}" />
</UserControl>
Aaaaaaand GO!
M
Unfortunately, there isn't a better way. You can override the OnContentTemplateChanged, instead of hooking up to the event.
You would need to use the DataTemplate.FindName method to get the actual element. The link has an example of how that method is used.
You would need to delay the call to FindName if using OnContentTemplateChanged though, as it is not applied to the underlying ContentPresenter immediately. Something like:
protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) {
base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);
this.Dispatcher.BeginInvoke((Action)(() => {
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
textBox.Text = "Found in OnContentTemplateChanged";
}), DispatcherPriority.DataBind);
}
Alternatively, you may be able to attach a handler to the LayoutUpdated event of the UserControl, but this may fire more often than you want. This would also handle the cases of implicit DataTemplates though.
Something like this:
public UserControl1() {
InitializeComponent();
this.LayoutUpdated += new EventHandler(UserControl1_LayoutUpdated);
}
void UserControl1_LayoutUpdated(object sender, EventArgs e) {
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
textBox.Text = "Found in UserControl1_LayoutUpdated";
}

Silverlight relativebinding ItemTemplate ListboxItem - Listbox

I created programatically a class (I called it ViewGrid) so that I use an instance of it as ItemTemplate for my ListBox control; of course, it's my data template for the listboxitem....
Also, in my ViewGrid class, I got a dependency property called IsChecked and I want to keep it in sync with the ListBoxItem's IsSelected property. I noticed that in SL there no relativesource-findancestor-ancestortype support for binding as in WPF, still, I need to find a way to keep my IsChecked property synchronized with the IsSelected property of the internally generated ListBoxItem for my ListBox control. Can you help?
Here is a ListBox defined in XAML that uses the IsSelected property of each LitBoxItem to show or hide a button when selected. You just need to duplicate that Binding approach for the ListBoxItems you create in code. Either that, or create a UserControl with the appropriate ListBoxItem XAML, and insert instances of those UserControls into your ListBox.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="200" Height="120">
<StackPanel Margin="5">
<TextBlock Text="{Binding Name, Mode=OneWay}" />
<StackPanel Visibility="{Binding IsSelected, Mode=OneWay, Converter={StaticResource BoolToVisible}}">
<Button Content="Show Details" Click="OnDetailsClick" Tag="{Binding}" />
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Good luck,
Jim McCurdy
Face To Face Software and YinYangMoney
UPDATE: I revisited this and found a much better solution. My original one remains below, but the way I actually ended up solving this problem is via using the ViewGrid in a ControlTemplate instead of a DataTemplate. Then you can use the RelativeSource TemplatedParent binding to bind to the IsSelected property of the ListBox. So, add the following to the Resources of the listbox or your page or user control:
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel>
<ViewGrid IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>​
<!-- other controls may go here -->
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ORIGINAL:
So after seven years, you almost certainly don't need an answer to this anymore... however, I recently spent a morning wrestling with this issue and thought I'd give my solution in case any similar unfortunate ends up here.
First off, anyone who's using Silverlight 5 is in luck as AncestorType is apparently now available for RelativeSource, letting you bind directly to the IsSelected property of the ListBoxItem. For those of us stuck with 4 or below, the only real workaround I came up with was "faking" the binding via use of events in the code behind.
To do this, assume you have your YourView XAML with a ListBox named "lbYourListBox" which has its ItemsSource and SelectedItem properties bound to appropriate properties on a YourViewModel class, along with a ViewGrid in its ItemTemplate whose IsChecked property is not bound to anything. Then, in your code behind file, you wire up events as follows:
public YourView()
{
InitializeComponent();
this.Loaded += (sender, e) =>
{
((YourViewModel)this.DataContext).PropertyChanged += vm_PropertyChanged;
UpdateViewGrids();
};
}
// this part propagates changes from the view to the view model
private void viewGrid_Checked(object sender, RoutedEventArgs e)
{
var selectedVM = ((ViewGrid)sender).DataContext as SourceItemType;
((YourViewModel)this.DataContext).SelectedViewGridItem = selectedVM;
}
private void vm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (string.Equals(e.PropertyName, "SelectedViewGridItem"))
{
UpdateViewGrids();
}
}
// this part propagates changes from the view model to the view
private void UpdateViewGrids()
{
var viewGrids = this.lbYourListBox.GetVisualDescendants().OfType<ViewGrid>();
var selectedVM = ((YourViewModel)this.DataContext).SelectedViewGridItem;
foreach (var grid in viewGrids)
{
grid.IsChecked = selectedVM == grid.DataContext;
}
}​
The viewGrid_Checked event handler should be wired up to the Checked event of the view grid in the ItemTemplate. The GetVisualDescendants() method comes from the Silverlight Toolkit.
Important caveats:
The ViewGrid.Checked event should not fire except for the unchecked->checked transition, and no more than one view grid should be able to be selected at once. If those two things aren't true, you'll have to make appropriate edits to ensure this code can't cause an infinite event-driven loop. (Of course, if you don't need two-way binding, you only need one of these event handlers and event ping-pong isn't a concern.)
I wrote this for a user control which had its data context set in XAML, which is why the event handler for the view model's PropertyChanged event is only assigned after the view is loaded. Depending on how and when your view and view model are bound to each other, you may have to assign that earlier/later/differently.
This won't work if the view grids aren't visible, GetVisualDescendants seems to ignore hidden/collapsed controls.

Clicking HyperLinks in a RichTextBox without holding down CTRL - WPF

I have a WPF RichTextBox with isReadOnly set to True. I would like users to be able to click on HyperLinks contained within the RichTextBox, without them having to hold down Ctrl.
The Click event on the HyperLink doesn't seem to fire unless Ctrl is held-down, so I'm unsure of how to proceed.
I found a solution. Set IsDocumentEnabled to "True" and set IsReadOnly to "True".
<RichTextBox IsReadOnly="True" IsDocumentEnabled="True" />
Once I did this, the mouse would turn into a 'hand' when I hover over a text displayed within a HyperLink tag. Clicking without holding control will fire the 'Click' event.
I am using WPF from .NET 4. I do not know if earlier versions of .NET do not function as I describe above.
JHubbard80's answer is a possible solution, it's the easiest way if you do not need the content to be selected.
However I need that :P here is my approach: set a style for the Hyperlinks inside the RichTextBox. The essential is to use a EventSetter to make the Hyperlinks handling the MouseLeftButtonDown event.
<RichTextBox>
<RichTextBox.Resources>
<Style TargetType="Hyperlink">
<Setter Property="Cursor" Value="Hand" />
<EventSetter Event="MouseLeftButtonDown" Handler="Hyperlink_MouseLeftButtonDown" />
</Style>
</RichTextBox.Resources>
</RichTextBox>
And in codebehind:
private void Hyperlink_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
var hyperlink = (Hyperlink)sender;
Process.Start(hyperlink.NavigateUri.ToString());
}
Thanks to gcores for the inspiaration.
Managed to find a way around this, pretty much by accident.
The content that's loaded into my RichTextBox is just stored (or inputted) as a plain string. I have subclassed the RichTextBox to allow binding against it's Document property.
What's relevant to the question, is that I have an IValueConverter Convert() overload that looks something like this (code non-essential to the solution has been stripped out):
FlowDocument doc = new FlowDocument();
Paragraph graph = new Paragraph();
Hyperlink textLink = new Hyperlink(new Run(textSplit));
textLink.NavigateUri = new Uri(textSplit);
textLink.RequestNavigate +=
new System.Windows.Navigation.RequestNavigateEventHandler(navHandler);
graph.Inlines.Add(textLink);
graph.Inlines.Add(new Run(nonLinkStrings));
doc.Blocks.Add(graph);
return doc;
This gets me the behavior I want (shoving plain strings into RichTextBox and getting formatting) and it also results in links that behave like a normal link, rather than one that's embedded in a Word document.
My answer is based on #BionicCode's answer, which I wanted to extend with the event handler code, which I had some difficulties to get it working.
<RichTextBox IsDocumentEnabled="True" IsReadOnly="True">
<FlowDocument>
<Paragraph>
<Run Text="Some editable text" />
<Hyperlink x:Name="DuckduckgoHyperlink"
NavigateUri="https://duckduckgo.com">
DuckDuckGo
</Hyperlink>
</Paragraph>
</FlowDocument>
</RichTextBox>
I changed his code slightly:
I wanted the RichTextBox to be readonly. When the RichTextBox is readonly, it is not necessary to put the HyperLink into a TextBlock. However, using TextBlock in a RichTextBlock where the user can make changes is a great suggestion.
In my programming style, code related stuff belongs in the code behind file. Event handlers are code and I prefer to even add the event handler to its control from code behind. To do that, it is enough to give the Hyperlink a name.
Code behind
I needed to display some rich text with links in a HelpWindow:
public HelpWindow() {
InitializeComponent();
DuckduckgoHyperlink.RequestNavigate += Hyperlink_RequestNavigate;
}
private void Hyperlink_RequestNavigate(object sender,
RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) {
UseShellExecute = true,
});
e.Handled = true;
}
Note that the same event handler can be used by any HyperLink. Another solution would be not to define the URL in XAML but hard code it in the event handler, in which case each HyperLink needs its own event handler.
In various Stackoverflow answers I have seen the code:
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
Which resulted in the error message:
System.ComponentModel.Win32Exception: 'An error occurred trying to start process 'https://duckduckgo.com/' with working directory '...\bin\Debug\net6.0-windows'. The system cannot find the file specified.'
Do not handle any mouse events explicitly and do not force the cursor explicitly - like suggested in every answer.
It's also not required to make the complete RichTextBox read-only (as suggested in another answer).
To make the Hyperlink clickable without pressing the Ctrl key, the Hyperlink must be made read-only e.g., by wrapping it into a TextBlock (or alternatively by making the complete RichTextBox read-only, of course).
Then simply handle the Hyperlink.RequestNavigate event or/and attach an ICommand to the Hyperlink.Command property:
<RichTextBox IsDocumentEnabled="True">
<FlowDocument>
<Paragraph>
<Run Text="Some editable text" />
<TextBlock>
<Hyperlink NavigateUri="https://duckduckgo.com"
RequestNavigate="OnHyperlinkRequestNavigate">
DuckDuckGo
</Hyperlink>
</TextBlock>
</Paragraph>
</FlowDocument>
</RichTextBox>
I changed EventSetter from #hillin's answer.
MouseLeftButtonDown didn't work in my code (.Net framework 4.5.2).
<EventSetter Event="RequestNavigate" Handler="Hyperlink_RequestNavigate" />
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
Process.Start(e.Uri.ToString());
}
If you want to turn Arrow into a Hand cursor always without default system navigation, below is the approach.
<RichTextBox>
<RichTextBox.Resources>
<Style TargetType="{x:Type Hyperlink}">
<EventSetter Event="MouseEnter" Handler="Hyperlink_OnMouseEnter"/>
</Style>
</RichTextBox.Resources>
</RichTextBox>
private void Hyperlink_OnMouseEnter(object sender, MouseEventArgs e)
{
var hyperlink = (Hyperlink)sender;
hyperlink.ForceCursor = true;
hyperlink.Cursor = Cursors.Hand;
}

Resources