WPF Listbox remove selection by clicking on blank space - c#

I have a wpf listbox with a custom item template which contains a rectangle.
The each item in the listbox can be selected (only one at a time).
I want to add a behavior in which when a user clicks on a place which isn't the item (for instance, a blank spot on the listbox, which is not an item), the selected item will become deselected.
Any ideas?
Thanks.
For example with a simple listbox:
item 1
item 2
The behavior that I'm looking for is when the user clicks on pixel 500 (which is a part of the listbox but not on an item), the currently selected item will be deselected.

The simple solution is to data bind a property to the ListBox.SelectedItem property and set it to null whenever you want to clear the selection:
<ListBox ItemsSource="{Binding YourItems}" SelectedItem="{Binding SelectedItem}"
SelectionMode="Single" />
Then in code, you can just do this to clear the selection:
SelectedItem = null;
And when would you do that? You can attach a handler to the PreviewMouseLeftButtonDown event of the Window, or any other control in your UI. In the handler method, you could do a hit test to see what the item the user clicked on was:
HitTestResult hitTestResult =
VisualTreeHelper.HitTest(controlClickedOn, e.GetPosition(controlClickedOn));
Control controlUnderMouse = hitTestResult.VisualHit.GetParentOfType<Control>();
See the VisualTreeHelper.HitTest Method (Visual, Point) for more help with this part.
Then maybe something like this:
if (controlUnderMouse.GetType() != typeof(ListBoxItem)) SelectedItem = null;
Of course, there are many ways to do this, and you'll have to fill in the few blank spots that I left, but you should get the idea.
EDIT >>>
The generic GetParentOfType method is a custom Extension Method that is defined in a separate class named DependencyObjectExtensions:
public static class DependencyObjectExtensions
{
public static T GetParentOfType<T>(this DependencyObject element)
where T : DependencyObject
{
Type type = typeof(T);
if (element == null) return null;
DependencyObject parent = VisualTreeHelper.GetParent(element);
if (parent == null && ((FrameworkElement)element).Parent is DependencyObject)
parent = ((FrameworkElement)element).Parent;
if (parent == null) return null;
else if (parent.GetType() == type || parent.GetType().IsSubclassOf(type))
return parent as T;
return GetParentOfType<T>(parent);
}
...
}

For The each item in the listbox can be selected (only one at a time).
You can come up with one of followings
1- Disable the item after it is selected.
2- Maintain a list at backend to mark each index selectable or unselectable.

To assure that only one item is selected put this in the listbox:
SelectionMode="Single"
then for the unselect when clicking somewhere, you can try to check this events
PreviewMouseLeftButtonUp
LostFocus()
Regards,

I was searching for a solution to this problem, but to prevent the last item in the listbox from becoming selected when clicking on the blank space. my problem is slightly different but has the same solution that I have come up with which works for me.
Although I am using powershell and not c#, I am still utilizing the windows forms listbox control so I think the idea will be applicable.
Also, I couldn't find any discussions of this problem specifically dealing with powershell when I was searching for a solution, so I wound up here.
so I created a variable, maxY, and multiplied the number of list items by the itemheight.
next, at the beginning of the mouse_up event, I just check if the Y location from the mouse click is less than the maxY variable. if true, select the item and run your code, if not, do nothing.
I can only provide a code sample in powershell, but I think the idea is portrayed.
$listbox.add_MouseUP({
$maxY = $this.items.count * $this.itemHeight
if ($_.y -le $maxY) {
$this.SelectedIndex = $this.IndexFromPoint($_.X, $_.y)
#do stuff here
}
else {
$this.clearselection()
}
}
This will clear all selections if clicking on blank space, but will also prevent an item from being selected when clicking on blank space.

Related

How to change focus from textbox of one ListViewItem to another in UWP?

I have a TextBox inside a ListView. I dynamically add ListView items on KeyDown event (on adding the new item to my observable collection a new ListView Item is created due to Two-Way Binding).
Now when a new element is added I want to set focus to the TextBox of newly created ListView item. It is a bit complex than I thought, Help me solve this problem.
I originally thought it could be possible to use ListView.ContainerFromItem to retrieve the newly added item and then use VisualTreeHelper to search for the TextBox within the template and focus it. It turns out however, that this solution does not work. After adding the item, the container for the list item is not materialized immediately (which is logical, as the control just got the notification about the collection change, but didn't have any time to build up the control hierarchy yet, as our code is still executing).
In fact, the problem has a far simpler solution. You can use the Loaded event on the TextBox within the template. This will be called only once, when the template is first materialized. This is not a perfect solution however, see update below.
In my example I have the following template:
<DataTemplate>
<Grid>
<TextBox Loaded="InputTextBox_Loaded" />
</Grid>
</DataTemplate>
And in the code-behind:
private void InputTextBox_Loaded(object sender, RoutedEventArgs e)
{
var textBox = (TextBox)sender;
textBox.Focus(FocusState.Programmatic);
}
Update: Virtualization
Turns out there is a catch - virtualization creates a number of copies of the template in memory (depending on the window size) to allow for comfortable scrolling, but after that it will just reuse the existing controls - and the Loaded event will never be called again - that's a problem. Luckily, we can solve this as well - instead of the Loaded event we will use DataContextChanged.
Updated XAML:
<DataTemplate>
<Grid >
<TextBox DataContextChanged="TextBox_DataContextChanged" />
</Grid>
</DataTemplate>
Updated code-behind:
private void TextBox_DataContextChanged(
FrameworkElement sender,
DataContextChangedEventArgs args)
{
var textBox = (TextBox)sender;
if ( args.NewValue == Items.Last())
{
//last item, focus it
textBox.Focus(FocusState.Programmatic);
}
}
Ok, that is better, we are getting there! Only one thing left to make it perfect. The current configuration means that once we scroll the last item into view, it will always get focus, which might not be what we want. Instead, we probably want this to happen only once - when the item is newly added. We can do so by adding a bool flag which we set to true when adding a new item into the collection and flip back to false when we focus it the first time:
//set this to true when a new item is being added to the collection
private bool _focusItem = true;
private void TextBox_DataContextChanged(
FrameworkElement sender,
DataContextChangedEventArgs args)
{
var textBox = (TextBox)sender;
if (args.NewValue == Items[Items.Count - 1] && _focusItem)
{
//last item, focus it
textBox.Focus(FocusState.Programmatic);
_focusItem = false;
}
}

Accessing UserControl when it's a DataTemplate in a ListBox

Fist time question long time user of Stack Overflow. I'm somewhat new to the whole MVVM space so I think my Google Fu is failing me. I apologize if I get some of these terms incorrect.
I currently have an observable list class:InstrumentExecutor.
This list is then set as the ItemsSource of a ListBox called InstrumentList.
The ListBox is setup to use a DataTemplate with a FrameworkElementFactory that uses the InstrumentControl UserControl to display the data.
DataTemplate code:
DataTemplate instrumentDataTemplate = new DataTemplate();
instrumentDataTemplate.DataType = typeof(InstrumentExecutor);
FrameworkElementFactory frameelement = new FrameworkElementFactory(typeof(InstrumentControl));
instrumentDataTemplate.VisualTree = frameelement;
InstrumentList.ItemTemplate = instrumentDataTemplate;
And that is all working just great! Data is flowing in and out and everyone is happy. Here's the part where I'm having trouble.
In the user control there is some Expander WPF code. I am wanting to send an event, call a method, do something to trigger the expansion or collapse from the parent ListBox to each and every item in this listbox. Whenever I iterate the InstrumentList.Items the data source (InstrumentExecutor) is the only data I see.
There's some ways I can hack around using properties and notifications in the data class (Instrument Executor) but I'd like to see if I can make it cleaner.
So is there a way I can access each UserControl? I've looked at ItemContainerGenerator but haven't had much luck.
Thanks for the help!
Update 1: Using the VisualTreeHelper idea and some StackOver during the usercontrol load event for each line item I can find the parent (code below) and build a linkage that way. Here's the code that goes with this idea.
public static T FindParent<T>(DependencyObject current) where T : class
{
DependencyObject parent = VisualTreeHelper.GetParent(current);
if (parent == null)
return null;
if (parent is T)
return parent as T;
else
return FindParent<T>(parent);
}
It works but it's not as sexy as I'm used to. Next up is trying the Binding idea. Thanks!

Strange behavior by combo box selection in wpf

I am working on an application, wherein we have a main window which is having so many child windows in different dock option. So, one dock is having a property panel window which allows a user to modify property of selected entity and after changing the value user has to click on apply button which is available in the bottom of the control. So, I was willing to have some sort of functionality that if user has modified some value and instead of clicking on apply, if user click somewhere else apart from property panel view's sub control's, then user should be given a message that "Please first click apply to save your changes". for this what I did, I wrote the following piece of code on the mouse down event of MainWindow.
private void MainWindow_MouseDown(object sender, MouseButtonEventArgs e)
{
var hitObject = this.InputHitTest(e.GetPosition(this)) as DependencyObject;
if (hitObject.FindVisualAncestor<PropertyPanelUserControl>() == null)
{
MessageBox.Show("Please save your changes");
}
}
So, the logic is this, on mouse down of main window, get the hit object and check that if it is a child control of property panel control, then it will have PropertyPanelUserControl as its parent and other control which are not part or child control of PropertyPanelUserControl, then user will be prompted to click on the apply.
The above piece of code was working superb...but I figured out a strange issue, I had a combo box in the property panel which had entries from 1 to 10. So, when user tries to change value to other value, then user will not be given that message as, so far user is clicking on the property panel control and when i check the hit object in the mouse down event after selecting an item in the combo box, then hit object was the chromeButton or combo box. but When I selected the last item 10, then hit object comes as border which has the property panel control.
<Border><View:PropertyPanelControl/></Border> and above check fails, as border doesn't have ancestor as property panel control, rather border is the ancestor of the control. So, user gets a message even while changing only combo box value,
Moreover, I have made sure that I was clicking on the combo box item not outside, So, now question is this why wpf is behaving in this weird way and how to address this issue.
Your first question is strange:
why wpf is behaving in this weird way
You described what happens and it all seems totally normal to me. The user clicks on a ComboBoxItem and your HitTest tells you that you've clicked on a ComboBoxItem... I don't see any problem there.
how to address this issue
Now I imagine that if you had taken that ComboBoxItem and worked your way up the visual tree, then you would have found your PropertyPanelUserControl control. Try something like this instead:
HitTestResult result = VisualTreeHelper.HitTest(this, e.GetPosition(this));
UIElement uIElement = result.VisualHit.GetParentOfType<PropertyPanelUserControl>();
if (uIElement != null)
{
// the user clicked inside the PropertyPanelUserControl control
}
The GetParentOfType method is an Extension method that I created that walks up the visual tree looking for the first element of a particular type... you can easily refactor it into a normal method if you prefer:
public static T GetParentOfType<T>(this DependencyObject element) where T : DependencyObject
{
Type type = typeof(T);
if (element == null) return null;
DependencyObject parent = VisualTreeHelper.GetParent(element);
if (parent == null && ((FrameworkElement)element).Parent is DependencyObject) parent = ((FrameworkElement)element).Parent;
if (parent == null) return null;
else if (parent.GetType() == type || parent.GetType().IsSubclassOf(type)) return parent as T;
return GetParentOfType<T>(parent);
}

WPF TabControl - Cannot programmatically select tabs

I have a user interface with a TabControl that initially displays a start page. Other items can be added to it by double-clicking on content in, for example, a DataGrid. New tabs should be selected when they are created. If the document corresponding to the item in the grid is already open, then the existing tab for that document should be opened rather than creating a new one.
I know that I should be able to programmatically select a tab by setting the TabControl's SelectedItem or SelectedIndex properties. However, the desired tab never actually activates. If I set one and then inspect the TabControl's state in the debugger, then both fields seem to update properly. However, after I continue execution, I see that the selected tab remains unchanged in the UI, and if I pause and inspect the TabControl's state again I see that the SelectedItem and SelectedIndex have returned to their previous values. Selecting a tab by clicking on it in the UI, on the other hand, works just fine.
Here's the declaration for the TabControl:
<TabControl x:Name="Tabs" >
<TabItem x:Name="StartPageTab" Header="Start Page" DataContext="{Binding Path=StartPageViewModel}">
...
</TabItem>
</TabControl>
And the code for adding and selecting tabs:
private void _SelectTab(MyViewModel model)
{
TabItem tab;
if (_TryFindTab(model, out tab)) Tabs.SelectedItem = tab;
}
private bool _TryFindTab(MyViewModel target, out TabItem tab)
{
foreach (TabItem item in Tabs.Items)
{
MyViewModel model = item.DataContext as MyViewModel;
if (model != null && model.Equals(target))
{
tab = item;
return true;
}
}
tab = null;
return false;
}
private void _AddTab(MyViewModel model)
{
TabItem tab = new TabItem { DataContext = model, Content = new MyView() };
Binding bind = new Binding { Source = model, Path = new PropertyPath("Name") };
tab.SetBinding(TabItem.HeaderProperty, bind);
Tabs.Items.Add(tab);
Tabs.SelectedItem = tab;
}
It turned out to be related to something I conveniently omitted from the original problem description:
The DataGrid in question was in the content for StartPageTab. I was handling double-clicks on that DataGrid by capturing its MouseDoubleClick event, searching the visual tree to find what DataGridRow was double-clicked (if any), and then raising an event that would eventually be captured by the main window, which would respond by calling either _SelectTab or _AddTab, depending on whether the document in question was already open.
At which point, the call stack would unroll and get back to that MouseDoubleClick event handler. In that handler, I forgot to set the MouseButtonEventArgs's Handled property to true. So WPF kept searching for someone else to handle that click event - and the element that it eventually found would respond by asking for focus, which in turn meant that the original tab needed to get focus back.
Adding e.Handled = true; stopped that whole mess in its tracks, so the new tab could stay selected.
You could try using tab.focus()
I have tabs in my application and this is a quick way to make your selected tab visible.
Have you tried binding to TabItem.IsSelected and updating that in you view model?
In an older C# app I had, using page controls, I was able to force the page active by telling the tab control object to select the tab...
MyTabControlWithMultiplePages.SelectTab(PageIWantShown);

How to programmatically set selected Panorama item in WP7

I'm using a panorama control in a WP7 app. One of the PanoramaItems takes you to another page, which then allows you send an email through the EmailComposeTask. If you don't select to send the email and press the back button, the Panorama returns to the item you last selected. However, if you do select to send an email (and therefore leave the app), it does not return to the previously selected PanoramaItem. Instead, it returns to the first item in the Panorama. I tried keeping track of the selected index and setting it, but I got an error saying the SelectedIndex is not settable. This is confirmed on MSDN documentation http://msdn.microsoft.com/en-us/library/microsoft.phone.controls.panorama.selectedindex%28VS.92%29.aspx
Is there any way to manually set the selected index/item on a panorama? If not, is there a way for it to remember what was selected, even if the user leaves the app to compose an email?
I'm not sure if you can programmatically force an animation to another PanoramaItem, but you can change the Panorama.DefaultItem.
So you might have 3 PanoramaItem's and on the OnNavigatedTo() handler, change the default item via:
panoramaControl.DefaultItem = panoramaControl.Items[indexToSet];
This should help when you recover from a tombstone.
You could try the solution posted by Silicon Shark in this thread. It's noted to work, but only on the initial display - which shouldn't be a problem for your requirements of restoring state after tombstoning.
How to programmatically set the visible item in a Panorama control?
You can get the currently active page from the panorama's SelectedIndex property.
Unfortunately setting DefualtItem is only an approximation to solving this problem, which you may have discovered already.
Edit: Be aware that setting DefaultItem, changes which page of the panorama is the first page. It's a subtle difference, but you will see how it matters looking at the positioning of the heading and the wrap around of the background image.
Here is a solution. It does work as expected and does not rearrange your panorama, so your user interface is consistent.
pan.SetValue(Panorama.SelectedItemProperty, panoramaItem);
Panorama temp = pan;
LayoutRoot.Children.Remove(pan);
LayoutRoot.Children.Add(temp);
LayoutRoot.UpdateLayout();
this is not a perfect solution in that it does not slide nicely like panorama should, and it is probably not very efficient, but on the other hand you are not changing the default item so your user interface stays consistent.
I tested solutions listed here without success. Here is what I did that works like a charm!
PanoramaItem panItem = (PanoramaItem)panorama.Items[1];
panorama.Items.Remove(panItem);
panorama.Items.Insert(0, panItem);
You need to remove the panel from the list and re-inserting it at the desired position!
Set new selected item by
pan.SetValue(Panorama.SelectedItemProperty, pan.Items[newSelectedItem]);
However, it work only on the initial so my idea is let the panorama control re-init when we change the selected item. This is my code, just add this after Panorama.SelectedItem changing.
(pan.Items[curIndex] as PanoramaItem).Visibility = Visibility.Collapsed;
pan.SetValue(Panorama.SelectedItemProperty, pan.Items[(curIndex + 1) % pan.Items.Count]);
pan.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
(pan.Items[curIndex] as PanoramaItem).Visibility = Visibility.Visible;
But there is not transition effect now! Although, you can create your self.
It work great for me, this page also create a effect for sliding right http://xme.im/slide-or-change-panorama-selected-item-programatically
I'm using this model to change to a pivot when the device goes into landscape view, I'll probably end up extracting the current item to the application state. The panorama is a no-go in landscape orientation.
private int hub_page_index;
protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
base.OnOrientationChanged(e);
if (panorama.Visibility == Visibility.Visible)
{
hub_page_index = panorama.SelectedIndex;
}
else if (pivot.Visibility == Visibility.Visible)
{
hub_page_index = pivot.SelectedIndex;
}
if (e.Orientation == PageOrientation.Landscape
|| e.Orientation == PageOrientation.LandscapeLeft
|| e.Orientation == PageOrientation.LandscapeRight)
{
// Display Pivot in Landscape orientation
pivot.SetValue(Pivot.SelectedItemProperty, pivot.Items[panorama.SelectedIndex]);
panorama.Visibility = Visibility.Collapsed;
pivot.Visibility = Visibility.Visible;
}
else
{
// Display Panorama in Portrait orientation
panorama.SetValue(Panorama.SelectedItemProperty, panorama.Items[pivot.SelectedIndex]);
pivot.Visibility = Visibility.Collapsed;
panorama.Visibility = Visibility.Visible;
}
}

Categories

Resources