DoubleClick Event for ListViewItem in WPF - c#

I know this was already asked a lot, but I didn't find any solution.
My ListView looks like this
<ListView Margin="0,0,0,0" x:Name="ContactListView" BorderBrush="Black" ItemsSource="{Binding RosterItemX}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="25">
<Image Tag="{Binding Availability}" Margin="0,0,5,0" Width="16" Height="16" VerticalAlignment="Center">
</Image>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<Custom:EventToCommand Command="{Binding ContactDblClicked, Mode=OneWay}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
I have my ICommand in my viewmodel:
public ICommand ContactDblClicked { get { return new RelayCommand<MouseButtonEventArgs>(contactDblClicked); } }
This event fires everytime someone double clicks into the ListView. Doesn't have to be on a ListViewItem.
I can handle the case when no ListViewItem is selected.
I cast (ListView)e.Source, and check if an Item is selected.
I need a way to check if what is double clicked is actually a ListViewItem and not empty space.

Not entirely sure what you mean by your last line I need a way to check if what is double clicked is actually a ListViewItem and not empty space., but here are two suggestions:
First, if you want to check if an item is selected in your ListView:
private void contactDblClicked(MouseButtonEventArgs obj)
{
var listView = obj.Source as ListView;
if (listView != null)
{
if (listView.SelectedItem != null)
{
Debug.WriteLine("item selected");
}
else
{
Debug.WriteLine("item not selected");
}
}
}
However, I think you already got that solution and if I understand your question right, you want to check if the user actually clicked an item (and not whitespace) even if an item is selected.
So here is the second approach to check if an item was really clicked:
private void contactDblClicked(MouseButtonEventArgs obj)
{
if (((FrameworkElement) obj.OriginalSource).DataContext is YourRosterItemXType)
{
Debug.WriteLine("item was *really* clicked");
}
}
Where YourRosterItemXType is the type of your binded RosterItemX property. With the above code you check, if the DataContext of the original source of the mouse event is set to YourRosterItemXType. Items in your ListView have that DataContext set and so you check if that mouse event comes really from one of those list items.

Related

How do I handle SelectionChange when the ComboBox is

I have a ListBox, where the list element has a ComboBox, a TextBox and a slider. Depending on the selction of the ComboBox either the TextBox or the slider should be visible.
<ListBox Name="lstPWM" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<!-- more definitions -->
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding Path=Gebertyp, Converter={local1:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectionChanged="PWMTyp_SelectionChanged"
SelectedValue="{Binding Path=Gebertyp}" />
<TextBox Visibility="{Binding GeberVisible}" Text="{Binding GeberNmr, Mode=TwoWay}"/>
<Slider Visibility="{Binding WertVisible}" Value="{Binding Wert, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The code behind is:
public partial class MainWindow : Window
{
public ObservableCollection<PWMKanal> PWM_col { get; set; } = new();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
lstPWM.ItemsSource = PWM_col;
foreach (var item in Board.PWM) PWM_col.Add(item); //Board.PWM is the data source.
}
private void PWMTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox box = sender as ComboBox; // Finding the line in the ListBox.
PWMKanal PWM = box.DataContext as PWMKanal;
int z = PWM_col.IndexOf(PWM);
Board.PWM[z].Gebertyp = (QuellePWM)box.SelectedValue;
if (Board.PWM[z].Gebertyp == QuellePWM.Sender)
{
PWM_col[z].GeberVisible = Visibility.Visible; // I thought that i may change the
PWM_col[z].WertVisible = Visibility.Hidden; // ObservableColelction directly
} // but the display is not updated.
else // In Debug mode i see, that PWM_coll
{ // is changed as expected, but no effect
PWM_col[z].GeberVisible = Visibility.Hidden; // on the GUI.
PWM_col[z].WertVisible = Visibility.Visible;
}
if (PWM_col.Count != 0) // this code is intended to update the GUI, but every time
{ // a new item is added the Selection Change fires again
PWM_col.Clear(); // and i get a stack overflow in an endless loop.
foreach (var item in Board.PWM) PWM_col.Add(item);
}
}
}
The comments describe my approaches and problems:
I change the selected element of the ObservableCollection directly, but this has no effect on GUI. At least tho code doesn't crash.
I clear the list ObservableCollection PWM_col, but then i get an infinite loop: every time an element is added to the list the SelectionChange event fires, calling the routin again. Result is stack overflow.
Now my questions to my approaches:
Is it possible to change an element of an ObservableCollection directly by code, and the display is automatically refreshed?
Is it possible to somehow catch the SelectionChanged event before the handler is executed? Or is it possible to temporary dissable the event?
Any other idear?
Thank you for your help!
CollectionChanged does notify, that collection itself, not the
single items, is changed. Therefore to see the changes item's
property need to implement INotifyPropertyChanged. Also remove Mode=OneTime
You can of course set the flag, that PWMTyp_SelectionChanged is
running:
private bool selChangedIsRunning = false;
private void PWMTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(selChangedIsRunning) return;
selChangedIsRunning = true;
// do stuff ....
selChangedIsRunning = false;
}
Other idea is - don't use the SelectionChange event, but do bind
Slider.Visibility and TextBox.Visibility to the
ComboBox.SelectedValue and use value converter to define the
Visibilty, also you can use the ConverterParameter.
<ComboBox x:Name="CmbPWMTyp" ItemsSource="{Binding Path=Gebertyp, Converter={local1:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectionChanged="PWMTyp_SelectionChanged"
SelectedValue="{Binding Path=Gebertyp}" />
<TextBox Visibility="{Binding ElementName=CmbPWMTyp, Path=SelectedValue, Converter={StaticResource YourConverter}, ConverterParameter=TBX}" Text="{Binding GeberNmr, Mode=TwoWay}"/>
<Slider Visibility="{Binding ElementName=CmbPWMTyp, Path=SelectedValue, Converter={StaticResource YourConverter}, ConverterParameter=SLDR}" Value="{Binding Wert, Mode=TwoWay}"/>
This link can be also very helpful for you: Difference between SelectedItem SelectedValue and SelectedValuePath

Get the row details when a control in data template is selected

Im building a WPF application and trying to stick to the MVVM pattern as much as possible. I have a list box with a data template inside of it that contains a TextBlock and Button. If the button within the data template is clicked it does not select the entire row, so I am unaware of what row it pertains to. I would like to grab the entire object and bind it to a property in the view model. Can I get some help or a workaround for this please that sticks to mvvm pattern.
List box with item template
<telerik:RadListBox Width="200" Height="150" HorizontalAlignment="Left" Margin="10" ItemsSource="{Binding ListOfSupplierInvoices}"
SelectedItem="{Binding SelectedSupplierInvoice, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<telerik:RadListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" >
<TextBlock Text="{Binding InvoiceNumber}" HorizontalAlignment="Left" Margin="5" ></TextBlock>
<telerik:RadButton Height="20" >
<telerik:RadButton.Content>
<Image Source="/ImageResources/Misc/delete.png" Stretch="Fill" />
</telerik:RadButton.Content>
</telerik:RadButton>
</StackPanel>
</DataTemplate>
</telerik:RadListBox.ItemTemplate>
</telerik:RadListBox>
How it looks in the view:
As far as I understand your code, the button corresponds to a delete command, which means you want to delete the item associated with the button. In this case, the selection might not need to change, you just have to pass the current item to the delete command.
Add a Delete command to your view model like this:
public class MyViewModel : ViewModelBase
{
public MyViewModel()
{
Delete = new DelegateCommand(ExecuteDelete, CanExecuteDelete);
// ...other code.
}
public ICommand Delete { get; }
private void ExecuteDelete(object obj)
{
var invoiceItem = (InvoiceItem) obj;
// Use this only if you need the item to be selected.
// SelectedSupplierInvoice = invoiceItem;
// ...your delete logic.
}
private bool CanExecuteDelete(object obj)
{
// ...your can execute delete logic.
}
// ...other code.
}
Note that I introduced InvoiceItem as item type, because I do not know your item type, simply adapt it. The Delete command gets the current item passed as parameter. If you can always remove the item, there is no need in selecting it, as it is gone afterwards.
Otherwise, uncomment the line so the SelectedSupplierInvoice is set to the item which will automatically update the user interface through the two-way binding if you have implemented INotifyPropertyChanged correctly or derive from ViewModelBase which exposes the RaisePropertyChanged or OnPropertyChanged method, e.g.:
private InvoiceItem _selectedSupplierInvoice;
public InvoiceItem SelectedSupplierInvoice
{
get => _selectedSupplierInvoice;
set
{
if (_selectedSupplierInvoice == value)
return;
_selectedSupplierInvoice = value;
RaisePropertyChanged();
}
}
In your XAML wire the button to the Delete command on the DataContext of the RadListBox.
<telerik:RadButton Height="20"
Command="{Binding DataContext.Delete, RelativeSource={RelativeSource AncestorType={x:Type telerik:RadListBox}}}"
CommandParameter="{Binding}">
<telerik:RadButton.Content>
<Image Source="/ImageResources/Misc/delete.png" Stretch="Fill" />
</telerik:RadButton.Content>
</telerik:RadButton>

WPF: Easiest way to add control to grid based on ListView selection

I have a ListView which contains items that should represent certain settings, which can be changed by the user. I am looking for the easiest way to link the ListViewItem to a custom user control.
What I have now:
<ListView
Grid.Row="0"
Grid.Column="0"
SelectionChanged="SettingsListViewSelectionChanged">
<ListViewItem x:Name="PathSettings" Content="Path"/>
<ListViewItem x:Name="HideShowTvShows" Content="Hide/Show TV Shows"/>
</ListView>
And then in the code behind I figure out which item was clicked, and attach the corresponding UserControl to the Grid.
private void SettingsListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = e.Source as ListView;
if (listView != null)
{
SettingsContentPanel.Children.Clear();
if (listView.SelectedItem.Equals(_pathSettings))
{
SettingsContentPanel.Children.Add(_pathSettings);
_pathSettings.SetValue(Grid.RowProperty, 0);
_pathSettings.SetValue(Grid.ColumnProperty, 1);
}
if (listView.SelectedItem.Equals(_hideShowTvShowsSettings))
{
SettingsContentPanel.Children.Add(_hideShowTvShowsSettings);
_hideShowTvShowsSettings.SetValue(Grid.RowProperty, 0);
_hideShowTvShowsSettings.SetValue(Grid.ColumnProperty, 1);
}
}
}
And the Grid itself:
<Grid
x:Name="SettingsContentPanel"
Grid.Row="0"
Grid.Column="2"
Grid.ColumnSpan="2" />
Is there a way to get rid of the boiler plate code behind and use XAML for this purpose?
Looks like you're looking for a ContentPresenter:
<ContentPresenter x:Name="SettingsContentPanel"
Content="{Binding SelectedItem, ElementName=MyLstView}"/>

WPF: ListView SelectedItem binding works for a split second, then changes back

let me start by introducing my current setup:
I have a ListView that binds its SelectedItem property to the ViewModel, like this:
<ListView Name="FileListView" ItemsSource="{Binding ImageList}"
SelectionChanged="ImageSelectionChanged"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<view:FileListItem />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
It's item template (view:FileListItem) is the following:
<Grid MouseDown="FileListItemMouseDown" KeyDown="FileListItemKeyDown">
....
<TextBlock Name="NewNameTextBlock"
Text="{Binding NewName}"
Grid.Column="2"
Visibility="{Binding TextBlockVisibility}" />
<TextBox Name="NewNameTextBox"
Text="{Binding NewName, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="2"
Visibility="{Binding TextBoxVisibility}" />
</Grid>
The idea here is to switch on the TextBox and switch off the TextBlock when the corresponding ListView item is being edited. This works ok, but when I hit a particular key, I want the ListView to select the next item and put that item into editing mode. I catch the KeyDown event as seen above in the ItemTemplate and broadcast a message, which is caught in the DataContext of my ListView like this:
public ImageFile SelectedItem {
get { return _selectedItem; }
set { _selectedItem = value; NotifyPropertyChanged("SelectedItem"); }
}
public void SelectAndEditThisHandler (object x)
{
ImageFile file = x as ImageFile;
SelectedItem = file;
}
The result is that the selection actually changes for a split second, but then it changes back to the previous selection. I suspect some other UI elements might be handling my key-presses and doing something to change the selection back, but I can't figure out which elements and how to pinpoint them.
Any help would be greatly appreciated! Thanks!
EDIT:
As requested, the SelectionChanged handler:
private void ImageSelectionChanged(object sender, SelectionChangedEventArgs e)
{
System.Collections.IList filelist = FileListView.SelectedItems;
if (filelist.Count == 1)
{
ImageFile selectedFile = FileListView.SelectedItem as ImageFile;
Mediator.Instance.NotifyColleagues(Mediator.Operations.ImagePathSelected, selectedFile.OriginalPath);
}
}
The mediator message broadcast doesn't do anything related to these controls/this problem at all.

Get selected item from a ListBox at hold

I've got a ListBox in a WP7 App where I want to do something with an item when the user hold it. The event work's great. My hold method gets called, but I can't detect which element in the list was hold.
ListBox.SelectedItem is always -1 and a code from another post on stackoverflow doens't work:
FrameWorkelement element = (FrameworkElement) e.OriginalSource;
ItemViewModel item = (ItemViewModel) element.DataContext;
I get an InvalidCastException when running it in the second line.
The following code should work.
private void StackPanel_Hold(object sender, GestureEventArgs e)
{
ItemViewModel itemViewModel = (sender as StackPanel).DataContext as ItemViewModel;
string t = itemViewModel.LineOne;
}
Note: before using the DataContext of the sender object, make sure you cast the sender object to the correct class. In this example I use a StackPanel in my DataTemplate:
<ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Height="78" Hold="StackPanel_Hold">
<TextBlock Text="{Binding LineOne}" />
<TextBlock Text="{Binding LineTwo}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories