Nobody asked that before:
What is an efficient way to avoid the expansion of certain TreeNode class descendants in a WinForms TreeView when the user does the "Expand all" thing, but still let him expand such nodes by clicking on the + symbol?
Sure I can handle BeforeExpand, but I have a hard time setting e.Cancel to true only if it is an ExpandAll operation. I wonder how I can determine this? I could subclass TreeView and override ExpandAll -- but that one cannot be overriden...
Seems like standard .NET treeview doesn`t have the way other than you described: trigger flag before ExpandAll, handle BeforeExpand and enable e.Cancel for appropriate nodes when flag is enabled.
As the ExpandAll method isn`t virtual you have these ways to follow:
Inherit from the TreeView class and add ExpandAllEx method where trigger this flag. No a good one because you need to cast to your tree class everywhere you use the tree instance.
Add an extension method for the TreeView class where use tree.Tag property for this flag. More useful way with minimal changes in existing code.
This works 100%. I think. Sigh.
Private Sub MyTreeViewExpandNodes(ByVal Nodes As TreeNodeCollection)
For Each Node As TreeNode In Nodes
If Not (TypeOf Node Is SpecialTreeNode) Then
Node.Expand()
MyTreeViewExpandNodes(Node.Nodes)
End If
Next
End Sub
Private Sub MyTreeView_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyTreeView.KeyDown
If e.KeyCode = Keys.Multiply Then
e.Handled = True
e.SuppressKeyPress = True
MyTreeViewExpandNodes(MyTreeView.Nodes)
End If
End Sub
Related
I want to collapse or expand all TreeView nodes under parent node if user holds down Left Control key and presses left mouse button on expansion arrow of tree view.
How do you do this in WPF? It's not as obvious as it was in WinForms.
Here's something that seems to work for me:
<TreeView TreeViewItem.Collapsed="TreeViewItem_Collapsed" TreeViewItem.Expanded="TreeViewItem_Expanded"/>
(The above is omitting things like ItemsSource and ItemTemplate for brevity)
Private Sub TreeViewItem_Expanded(sender As Object, e As RoutedEventArgs)
If Keyboard.IsKeyDown(Key.LeftCtrl) Then DirectCast(e.OriginalSource, TreeViewItem).ExpandSubtree()
End Sub
Private Sub TreeViewItem_Collapsed(sender As Object, e As RoutedEventArgs)
If Keyboard.IsKeyDown(Key.LeftCtrl) Then CollapseSubtree(e.OriginalSource)
End Sub
Private Sub CollapseSubtree(Item As TreeViewItem)
Item.IsExpanded = False
For Each Child In Item.ItemContainerGenerator.Items
CollapseSubtree(Item.ItemContainerGenerator.ContainerFromItem(Child))
Next
End Sub
Explanation
Since TreeViewItem.Expanded and TreeViewItem.Collapsed are both routed events, we can handle them at the TreeView level as they "bubble" up. All items in the TreeView will trigger either TreeViewItem_Collapsed or TreeViewItem_Expanded when they close or open, respectively.
In my quick tests, e.OriginalSource always referred to the TreeViewItem that was expanded/collapsed, but some more rigorous testing might be in order.
In the event handlers, we use Keyboard.IsKeyDown to determine whether the left control key is currently being pressed. If so, we take the appropriate action.
For expansion, TreeViewItem already has a convenient ExpandSubtree method, so we can just use that.
For whatever reason, there is no matching CollapseSubtree built in, so I made my own. At its heart, it's a simple recursive method that closes the node you give it and then runs itself again for all child nodes. The trick is in getting the child TreeViewItems (containers), instead of the data objects they are representing. To do this, I make use of the ItemContainerGenerator, which is the class that each TreeViewItem uses to create its children.
I am adding controls at run time in a WPF App, these are all different types of control so are passed as a UIElement. I then get the type of control and can set the properties using SetValue. As I have a lot of control types to implement what I am now trying to do is is add an event to each control but can only seem to do this using Addhandler which requires a lot of extra code for each control as shown below. I am looking for a solution to allow me to add the error handler to the UIElement without prior conversion using a Cast, I am not sure this is possible?
Select Case Control.GetType()
Case GetType(ExtendedTextBox)
Dim newControl As ExtendedTextBox = TryCast(Control, ExtendedTextBox)
'Dim newCtl As UIElement = TryCast(Control, ExtendedTextBox)
If newControl IsNot Nothing Then
newControl.SetValue(ExtendedTextBox.HorizontalAlignmentProperty, HorizontalAlignment.Left)
newControl.SetValue(ExtendedControlProperties.HighlightTextOnFocusProperty, True)
newControl.SetValue(ExtendedControlProperties.MandatoryProperty, True)
AddHandler newControl.HasErrors, AddressOf UpdateErrorStatus
End If
I was incorrectly prefixing my validation with "Customer." which as in inherited control was causing the issue, once removed it works as expected.
I'm trying to have my context menu stay on screen even after I click one of its dropdown items (when the shift key is pressed, though I don't think that matters to the issue). You can see an example of the behavior in Windows XP when you click on Start > All Programs > Accessories > [now hit your shift key] and click on Windows Explorer... The menu stays up.
It's a C# app, using Winforms, development machine is Windows 7, production is either XP, Vista or 7.
The toolstripmenuitem doesn't seem to have a closing event; only a closed one. Those familiar with a closing event will know that you can set a cancel flag to prevent the control from closing.
Also, when I try a workaround of remaking it visible from within either its click event or its closed event, it doesn't work. Although that would have been a tolerable workaround in the immediate, it is not for production.
Any suggestions or related info greately appreciated.
Thank you
I was able to have a dynamically created submenu of my ContextMenu stay on screen upon being clicked by setting the AutoClose property of its Parent DropDown menu to "False" like this:
ParentMenu.DropDown.AutoClose = false;
where ParentMenu is a ToolStripMenuItem.
The use of the Closing event of the DropDown's Parent ToolStripDropDownMenu to acheive this by setting a "Cancel" flag was not a viable solution because it caused inconsistant showing/hiding behavior in either of its 2 levels of Parent menus as well as causing unexpected visual artifacts on screen that I could not get rid of when later being hidden through code. It also seemed to cause certain events of the dynamically created menu's Parents to no longer fire, such as its MouseEnter event.
An interesting find during this experience was that although Visual Studio 2010's intellisense lists a LostFocus event for a DropDown of a context menu item; when adding this event to dynamically created menus it does not get fired; this is apparently a known behavior as mentionned here:
Here's what I ended up using. With this method, the dropdown's autoclose is only disabled while the mouse pointer is on the drop down control. MyMenuItem is type ToolStripMenuItem.
AddHandler MyMenuItem.DropDown.MouseEnter, AddressOf DisableDropMenuClose
AddHandler MyMenuItem.DropDown.MouseLeave, AddressOf EnableDropMenuClose
Private Sub DisableDropMenuClose(ByVal sender As System.Object, ByVal e As System.EventArgs)
CType(sender, ToolStripDropDownMenu).AutoClose = False
End Sub
Private Sub EnableDropMenuClose(ByVal sender As System.Object, ByVal e As System.EventArgs)
CType(sender, ToolStripDropDownMenu).AutoClose = True
End Sub
The ToolStripDropDownMenu has the Closing event.
private void MyContextMenuStrip_Closing(object sender, ToolStripDropDownClosingEventArgs e) {
if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked) {
e.Cancel = true;
}
}
I'm pondering taking the plunge to WPF from WinForms for some of my apps, currently I'm working on the combined barcode-reader/text-entry program (healthcare patient forms).
To be able to process the barcode characters, I rely on the Keypreview property in WinForms (because barcodes can be scanned regardless of what control has the focus).
But I cannot seem to find a KeyPreview property in neither VS2008 or VS2010, for a WPF app.
Is there an alternative approach/solution to handle my barcode characters in WPF?
Rgrds Henry
use the override in your own UserControls or Controls (this is an override from UIElement)
protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e) {
base.OnPreviewKeyDown(e);
}
if you want to preview the key down on any element which you dont create you can do this:
Label label = new Label();
label.PreviewKeyDown += new KeyEventHandler(label_PreviewKeyDown);
and then have a handler like so :-
void label_PreviewKeyDown(object sender, KeyEventArgs e) {
}
if you mark the event as handled (e.Handled = true;) this will stop the KeyDown event being raised.
Thanks got it working! Only problem was I'm coding in VB not C#, but the basic idea holds. Neat to create a label out of thin air and use it to insert yourself in the event stream.
If someone else is interested of the same solution but in VB for WPF, here's my test program, it manages to toss all 'a' characters typed, no matter what control has the focus:
Class MainWindow
Dim WithEvents labelFromThinAir As Label
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
AddHandler MainWindow.PreviewKeyDown, AddressOf labelFromThinAir_PreviewKeyDown
End Sub
Private Sub labelFromThinAir_PreviewKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
TextBox1.Text = e.Key ' watch 'em coming
If (44 = e.Key) Then e.Handled = True
End Sub
End Class
P.S. This was my first post on stackoverflow, really a useful site. Perhaps I'll be able to answer some questions in here myself later on :-)
WPF uses event bubbling and tunneling. In other words the events travel down and up the visual element tree. Some events will have a corresponding Preview event. So MouseDown will have a PreviewMouseDown that you can respond to. Check out this link and scroll down to the WPF Input Events section.
I'd like to enable/disable some other controls based on how many items are in my ListView control. I can't find any event that would do this, either on the ListView itself or on the ListViewItemCollection. Maybe there's a way to generically watch any collection in C# for changes?
I'd be happy with other events too, even ones that sometimes fire when the items don't change, but for example the ControlAdded and Layout events didn't work :(.
#Domenic
Not too sure, Never quite got that far in the thought process.
Another solution might be to extend ListView, and when adding and removing stuff, instead of calling .items.add, and items.remove, you call your other functions. It would still be possible to add and remove without events being raised, but with a little code review to make sure .items.add and .items.remove weren't called directly, it could work out quite well. Here's a little example. I only showed 1 Add function, but there are 6 you would have to implement, if you wanted to have use of all the available add functions. There's also .AddRange, and .Clear that you might want to take a look at.
Public Class MonitoredListView
Inherits ListView
Public Event ItemAdded()
Public Event ItemRemoved()
Public Sub New()
MyBase.New()
End Sub
Public Function AddItem(ByVal Text As String) As ListViewItem
RaiseEvent ItemAdded()
MyBase.Items.Add(Text)
End Function
Public Sub RemoveItem(ByVal Item As ListViewItem)
RaiseEvent ItemRemoved()
MyBase.Items.Remove(Item)
End Sub
End Class
I can't find any events that you could use. Perhaps you could subclass ListViewItemCollection, and raise your own event when something is added, with code similar to this.
Public Class MyListViewItemCollection
Inherits ListView.ListViewItemCollection
Public Event ItemAdded(ByVal Item As ListViewItem)
Sub New(ByVal owner As ListView)
MyBase.New(owner)
End Sub
Public Overrides Function Add(ByVal value As System.Windows.Forms.ListViewItem) As System.Windows.Forms.ListViewItem
Dim Item As ListViewItem
Item = MyBase.Add(value)
RaiseEvent ItemAdded(Item)
Return Item
End Function
End Class
I think the best thing that you can do here is to subclass ListView and provide the events that you want.