I want to run some code when the user single clicks on any given ListBox item. My setup is a ListBox with a custom ItemsPanelTemplate (Pavan's ElementFlow). Based on the position data that comes in to MouseLeftButtonDown is there a way to tell which item is was clicked? This is made a bit more difficult (or more confusing) by the custom ItemsPanelTemplate.
You can have an ItemContainerStyle, and specify an EventSetter in it:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseLeftButtonDown" Handler="ListBoxItem_MouseLeftButtonDown" />
...
Then, in the handler of the MouseLeftButtonDown, the "sender" will be the ListBoxItem.
ALSO, if you don't want to use this method, you can call HitTest to find out the Visual object at a specified position:
HitTestResult result = VisualTreeHelper.HitTest(myCanvas, pt);
ListBoxItem lbi = FindParent<ListBoxItem>( result.VisualHit );
public static T FindParent<T>(DependencyObject from)
where T : class
{
T result = null;
DependencyObject parent = VisualTreeHelper.GetParent(from);
if (parent is T)
result = parent as T;
else if (parent != null)
result = FindParent<T>(parent);
return result;
}
Related
How do I prevent a ListViewItem from being selected when I click on an item and press (Down/Up) Arrow? I don't want to disable it, I just want it to not be selectable on Shift+Arrow. Some of my listviewitems need to be selectable and some others not.
Sample:
[ListViewItem 1]
[ListViewItem 2]
[ListViewItem 3]
User clicks on ListviewItem1, it gets selected.
User then presses SHIFT+DOWN ARROW,=> ListViewItem 2 is not selected.
At this point either ListViewItem3 is selected or it might take another SHIFT+DOWN ARROW to select ListViewItem3 (it doesn't matter as long as it gets selected sometime).
(In my project the actual ListViewItem 2 is actually NOT a regular item on 1 row, it is a vertical item that spans several rows in just one column. My ListView source has a compositecollection of very different items; I am trying to select what appears to be 'regular row items' only)
You can accomplish this by using a Property of your class such as IsSelected. Here is some sample code for what that Property would look like.
public bool IsSelected
{
get
{
return isSelected; //return the value of isSelected
}
set
{
if (isSelectable) //if the item is allowed to be selected
{ //then update the value of isSelected
isSelected = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
}
In this example it is assumed that "isSelectable" is a value that is set in the constructor when your class is being initialized. If you don't already have INotifyPropertyChanged implemented, it needs to be... and the PropertyChanged event needs to be declared.
In addition, you will also want a Style that binds the selection of ListView items to this property. Here is an example of a Style that could accomplish this.
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.Resources>
You can take this one step further, and also bind to the Focusable Property. This will make it so that when you press SHIFT+DOWN it will skip any items that you cannot select and select the next item that is selectable. I would accomplish this in a manner similar to the following.
public bool Focusable
{
get
{
return isSelectable; //if the item is selectable, then it is focusable
}
}
You would also need to create a binding for this in your Style by creating an additional Setter. This could be done the following way, inside the same Style as before.
<Setter Property="Focusable" Value="{Binding Focusable}"/>
Try it both with and without that last Setter and see which implementation suits your needs best.
--------- UPDATE ---------
If you would like to only select items on mouse click, you could do so by subscribing to the ListView PreviewMouseLeftButtonDown event. Inside of the event handler, you would need to first get the clicked item, and call a function on your class which will override the selection. Here is an example of how that is done.
This function would exist in your UI code-behind and must be subscribed to by your ListView:
private void myListView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is ListViewItem))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
object obj = myListView.ItemContainerGenerator.ItemFromContainer(dep);
if (obj is MyClass)
{
foreach (MyClass i in myListView.Items)
i.OverrideSelect(false); //unselect all items
MyClass item = (MyClass)obj;
item.OverrideSelect(true); //select the item clicked
}
}
(Note: replace "MyClass" with your class type).
This function would exist in your class file:
public void OverrideSelect(bool selected)
{
isSelected = selected;
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
I've a datagrid with an ItemsSource of ObservableCollection (OC) of objects. When an item's property changes, I want to work on the OC itself .
E.g. I've an item which is approved for uploading to our database. However, I need to loop through the OC to check if other items exist in the collection which already fit the set criteria, so that I may actually not have to upload the selected item.
On the datagrid, when I tick the checkbox of an item, it will change the boolean value (e.g. "IsToUpload") of the item, and an event should trigger on the property change.
I'm assuming I will then need to 'bubble up' my event notifications to the datagrid/mainwindow class, where I can then work on the OC. How may I do this, and if this is not the correct way, what should I be doing?
I've followed Aran Mulholland's class structure to colour my rows dynamically: Coloring WPF DataGridRows one by one
So my class structure is roughly as follows:
MainWindow -> DataGrid
-> ObservableCollection<ItemObjectViewModel:NotificationObject>
ItemObject : INotifyPropertyChanged //this class is where I
//store my item variables. It is referenced through properties
//in the ItemObjectViewModel.
Event bubling \ routing etc works for dependency objects in a visual \ logical tree. Your NotificationObject is not a dependency object and neither is it hosted in the visual tree.... What we have in visual tree are the checkboxes (that are bound to your NotificationObject).
Non MVVM
In you DataGrid you would have to Tag your Checkboxes with some identification and then use ButtonBase.Click="" event at datagrid level which will be handled for any click event bubbled for any button based eleemnt (such as buttons, menuitems, togglebuttons, checkboxes, radioboxes, comboboxes) that gets clicked in the entire visual tree of the datagrid.
In the handler verify if the e.OriginalSource is a checkbox and that its Tag is same as the identification value we have set in the XAML of the datagrid. That way we know that the CheckBox is clicked.
E.g.
<DataGrid AutogenerateColumns="False"
ItemsSource="{Binding NotificationObjectCollection}"
ButtonBase.Clicked="OnNotificationCheckBoxClicked">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsClicked}"
Header="Click?">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Tag" Value="IsClickCheckBox" />
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
private void OnNotificationCheckBoxClicked
(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is CheckBox)
{
if (((CheckBox)e.OriginalSource).Tag == "IsClickCheckBox")
{
var notificationObject
= ((CheckBox)e.OriginalSource).DataContext
as NotificationObject;
if (notificationObject.IsClicked) { }
else { }
}
}
}
MVVM
The only way MVVM can notify the ancestor object in the visual is by using Command execution as the underlying NotificationObject gets checked (setter is called) we execute the command supplied to the NotificationObject.
Use the weak reference based RelayCommand or DelegateCommand (as available on the internet) for this purpose.
Add a new NotificationObject constructor
private ICommand _isClickedCommand;
public NotificationObject(ICommand isClickedCommand)
{
_isClickedCommand = isClickedCommand;
}
private bool _isClicked;
public bool IsClicked
{
get
{
return _isClicked;
}
set
{
if (_isClicked != value)
{
_isClicked = value;
OnPropertyChanged("IsClicked");
isClickedCommand.Execute(this);
}
}
}
Using the notification object
public class ItemObjectViewModel
{
private DelegateCommand<NotificationObject>
_notificationObjectClickedCommand
= new DelegateCommand<NotificationObject>(
OnNotificationObjectCommandExecute);
....
private void PopulateCollection()
{
NotificationObjectCollection
= new ObservableCollection<NotificationObject>();
NotificationObjectCollection.Add(
new NotificationObject(_notificationObjectClickedCommand));
}
private void OnNotificationObjectCommandExecute(
NotificationObject notificationObject)
{
if (notificationObject.IsClicked) { }
else { }
}
}
You can also achieve the ICommand based behavior in non MVVM scenario by using 'RoutedCommand'
Let me know if this helps...
I've found numerous sites that provide examples of how to add a custom spellchecking dictionary to an individual textbox like so:
<TextBox SpellCheck.IsEnabled="True" >
<SpellCheck.CustomDictionaries>
<sys:Uri>customdictionary.lex</sys:Uri>
</SpellCheck.CustomDictionaries>
</TextBox>
And I've tested this in my app and it works just fine.
However, I have industry specific jargon that I need to have ignored across ALL the textboxes in the application and applying this custom dictionary to each one individually seems to spit in the face of styles. At the moment I have a global textbox style to turn on spellchecking:
<Style TargetType="{x:Type TextBox}">
<Setter Property="SpellCheck.IsEnabled" Value="True" />
</Style>
I tried to do something like this to add the custom dictionary, but it doesn't like it, since the SpellCheck.CustomDictionaries is read only and setters only take writable properties.
<Style TargetType="{x:Type TextBox}">
<Setter Property="SpellCheck.IsEnabled" Value="True" />
<Setter Property="SpellCheck.CustomDictionaries">
<Setter.Value>
<sys:Uri>CustomSpellCheckDictionary.lex</sys:Uri>
</Setter.Value>
</Setter>
</Style>
I have done extensive searches looking for the answer to this, but all examples show only the one-use scenario in the specific textbox as cited in the first code block. Any help is appreciated.
I had the same issue and couldn't solve it with a style but created some code that accomplished the job.
First, I created a method to find all the textboxes contained within the visual tree of a parent control.
private static void FindAllChildren<T>(DependencyObject parent, ref List<T> list) where T : DependencyObject
{
//Initialize list if necessary
if (list == null)
list = new List<T>();
T foundChild = null;
int children = VisualTreeHelper.GetChildrenCount(parent);
//Loop through all children in the visual tree of the parent and look for matches
for (int i = 0; i < children; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
foundChild = child as T;
//If a match is found add it to the list
if (foundChild != null)
list.Add(foundChild);
//If this control also has children then search it's children too
if (VisualTreeHelper.GetChildrenCount(child) > 0)
FindAllChildren<T>(child, ref list);
}
}
Then, anytime I open a new tab/window in my application I add a handler to the loaded event.
window.Loaded += (object sender, RoutedEventArgs e) =>
{
List<TextBox> textBoxes = ControlHelper.FindAllChildren<TextBox>((Control)window.Content);
foreach (TextBox tb in textBoxes)
if (tb.SpellCheck.IsEnabled)
Uri uri = new Uri("pack://application:,,,/MyCustom.lex"));
if (!tb.SpellCheck.CustomDictionaries.Contains(uri))
tb.SpellCheck.CustomDictionaries.Add(uri);
};
I'm trying to build a set of typical CRUD maintenance forms in WPF - that are going to be pretty much the same except that they work on different database records.
Rather than creating a new window class for each, I'm trying to use a single window class that instantiated with a different ViewModel class for each database table, and for which I have a different UserControl defined for each ViewModel.
So, if I instantiate the window with its DataContext set to an instance of Record1ViewModel, I want to display it in the window using a Record1UserControl, if it's set to an instance of Record2ViewModel, I want to display it using a Record2UserControl.
I've verified that both user controls work fine, by defining them each directly in the window's XAML. But I've not figured out how to select one or the other, based on the type of the ViewModel.
This is not working:
<myWindow.Resources>
<DataTemplate x:Key="{x:Type ViewModels:Record1ViewModel}">
<MaintenanceControls:Record1 />
</DataTemplate>
<DataTemplate x:Key="{x:Type ViewModels:Record2ViewModel}">
<MaintenanceControls:Record1 />
</DataTemplate>
</myWindow.Resources>
<ContentPresenter Content="{Binding}" />
What I get, in the ContentPresenter, is the name of the type. The DataTemplates are not used.
Any ideas?
You can use the DataTemplateSelector to dynamically select a DataTemplate at run time something along the lines of
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is Task)
{
Task taskitem = item as Task;
if (taskitem.Priority == 1)
return
element.FindResource("importantTaskTemplate") as DataTemplate;
else
return
element.FindResource("myTaskTemplate") as DataTemplate;
}
return null;
}
}
How do I delete a selected ListViewItem from a WPF ListView when the ItemsSource is set to a DataView? I can get the ListViewItem that was selected and then how do remove the actual row in the DataView?
DataView dv = (DataView)myListView.ItemsSource;
ListViewItem lvi = (ListViewItem)myListView.ItemContainerGenerator.ContainerFromItem(myListView.SelectedItem);
<Delete ListViewItem here>
When you bind your collection to the listview, use ListCollectionView instead of DataView. Can be easily done like this (where dataView is of type DataView):
ListCollectionView lcv = new ListCollectionView(dataView);
myListView.ItemsSource = lcv;
Now when you need to delete any object, just do this:
ListCollectionView lcv = (ListCollectionView) myListView.ItemsSource;
lcv.Remove(myListView.SelectedItem);
And after deleting, just refresh the view:
lcv.Refresh();
or
((ListCollectionView)myListView.ItemsSource).Refresh();
Consider using the M-V-VM pattern to separate the notion of removing an item from your list of data objects and DIRECTLY removing them from your current UI implementation. The two do not need to know about each other, aside from Bindings.
When you use the MVVM pattern, expose a boolean "IsSelected" property in your ViewModel.
public class SimpleViewModel : BaseViewModel //For INotifyPropertyChanged, etc
{
public IList<SimpleBusinessObject> ViewModelItems;
public SimpleViewModel()
{
ViewModelItems = new ObservableList<SimpleBusinessObjectViewModel>();
}
}
public class SimpleBusinessObjectViewModel
{
public bool ViewModelIsSelected { get; set; }
public SimpleBusinessObjectViewModel()
{
ViewModelIsSelected = false;
}
}
Next, in your View try something like this:
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Setter Property="IsSelected" Value="{Binding ViewModelIsSelected}"
</Style.Triggers>
</Style>
<ListView ItemsSource={Binding ViewModelItems}>
//here you can insert how you want to display a ListViewItem
</ListView>
This will let you add, edit, and remove items in your ViewModel's List -- just like if it were the actual ListView. From here, you can also check each item's IsSelected (that responds to mouse interactions with the ListView) without actually checking the ListViewItem. This will be a much cleaner, maintainable solution.