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>
Related
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
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.
In my WPF project I'm using an ItemsControl to show items and delete/move up/down them:
<ItemsControl ItemsSource="{Binding TestList, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
...
<TextBox IsReadOnly="True" Text="{Binding Path=Value , Mode=OneWay}" />
<Button Content"Remove" Click="RemoveClick" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
private ObservableCollection<KeyValuePair<int, string>> TestList;
private void RemoveClick(object sender, RoutedEventArgs e)
{
var removebutton = sender as Button;
if (removebutton != null)
{
var test = removebutton.DataContext.ToString(); // That works
// var test removebutton.DataContext.Key;
}
}
I want to get the index (Key) of the selected ObservableCollection TestList item.
The removebutton.DataContext.ToString(); works fine, I get a string with key and value.
But I need only the Key and that doesn't work: removebutton.DataContext.Key; (Error: Cannot resolve symbol 'Key').
If I debug, I can access the Key:
You need to cast removebutton.DataContext to KeyValuePair<int, string>, since DataContext's type is object
This will work:
var test = ((System.Collections.Generic.KeyValuePair<int, string>)removebutton.DataContext).Key
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.
The sender of the event is the object inside the treeviewitem. However, it's not a dependencyobject so I don't think I can use ItemFromContainer to get the actual TreeViewItem. What should I do to get the actual treeviewitem that is selected?
i see that everyone is as confused by my question as I am by my problem.
according to the linked site, I can get my treeviewitem from the selecteditem property. However, when I attempt to do this I get null because the sender is a SignalViewModel object instead of a TreeViewItem.
TreeViewItem treeViewItem = this.AvailableSignalsTreeView.SelectedItem as TreeViewItem;
this returns null for me but the debugger shows that the selected item is of type SignalViewModel.
All I'm trying to do is get multiselect capability for a treeview, which I was going to do using an example I saw that just toggles the treeviewitems.
<TreeView
Grid.Row="0"
Background="Blue"
Foreground="Orange"
x:Name="AvailableSignalsTreeView"
SelectedItemChanged="AvailableSignalsTreeView_SelectedItemChanged"
ItemsSource="{Binding Source={StaticResource available_signals_source}}"
>
<TreeView.CommandBindings>
<CommandBinding Command="ApplicationCommands.SelectAll"
Executed="AvailableSignalsTreeView_SelectAll"
CanExecute="AvailableSignalsTreeView_SelectAllCanExecute"/>
</TreeView.CommandBindings>
<TreeView.InputBindings>
<KeyBinding
Command="ApplicationCommands.SelectAll"
Modifiers="Ctrl"
Key="A"
/>
</TreeView.InputBindings>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource ="{Binding Path = bits}" >
<TextBlock
Text="{Binding Path = SignalName}"
/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Path = BitNumber}"
/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Bits to Signal" Click="AddBitToSignal" />
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
Try this:
// Helper to search up the VisualTree
private static T FindAnchestor<T>(DependencyObject current)
where T : DependencyObject
{
do
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
}
while (current != null);
return null;
}
private void AvailableSignalsTreeView_SelectedItemChanged(
object sender,
RoutedPropertyChangedEventArgs<Object> e)
{
var treeViewItem = FindAnchestor<TreeViewItem>((DependencyObject)e.OriginalSource);
}
I think what I was thinking of is this. Basically the selected item isn't a dependency object so I can't traverse the tree using them, and I have to instead get the container using the itemcontainer generator.
I thought I tried this yesterday, not sure why it wasn't working.
TreeViewItem tvi = tv.ItemContainerGenerator.ContainerFromItem(tv.SelectedItem) as TreeViewItem;