Sorry I'm so late to the party, Josh.
I faced the same issue a while back and actually implemented a solution in which I used a static class called VirtualToggleButton to expose attached properties that would turn any input element (a TreeViewItem, for example) into a toggle button. In your case, the markup would look like this:
<TreeView ItemsSource="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding Children}">
<CheckBox Focusable="False"
x:Name="chk"
Content="{Binding Name}"
IsChecked="{Binding
IsChecked}"
/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type
TreeViewItem}">
<Setter
Property="dw:VirtualToggleButton.IsVirtualToggleButton"
Value="True" />
<Setter
Property="dw:VirtualToggleButton.IsChecked" Value="{Binding
IsChecked}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
I guess the en vogue term for this type of thing is "attached behavior". (Thank you Mr. Gossman... you've saved me from the overly wordy description about leveraging an attached property to extend a class in order to add support for yada yada yada...)
When the IsVirtualToggleButton property is attached to the TreeViewItem and set to true, the class monitors the TreeViewItem for input events (mouse clicks or key presses) and responds to those events just like a toggle button. A spacebar press or mouse click will update the attached IsChecked property. It even supports the IsThreeState property and raises all the expected toggle button events (Checked, Unchecked, and Indeterminate) on the target element. (Although, it doesn't include the ButtonBase events or properties... I'm too lazy for that.)
You will still need to set Focusable to false on the CheckBox. Any solution will likely require that because you'll never want the additional navigation stop for the CheckBox.
I'm happy to send you the code if you'd like. Just let me know and I'll dig it up. :-)
>
/>>
>
Dr. WPF - Online Office at http://www.drwpf.com/blog/
I agree that it would be nice if we could get this behavior by simply styling the existing TreeView/TreeViewItem, but unfortunately, without a multiselect TreeView, that’s not possible. (hint hint)
Just to follow up for the rest of the group, I've attached a version of Josh’s sample that uses the VirtualToggleButton class that I described earlier. After adding my existing class to the project, it really was just a matter of pulling out the command bindings and handlers and adding two lines of markup to the container style:
<Setter Property="dw:VirtualToggleButton.IsVirtualToggleButton" Value="True" />
<Setter Property="dw:VirtualToggleButton.IsChecked" Value="{Binding IsChecked}" />
(Okay, I might have mucked with the default dataset, but that was just for fun. J)
There are a few reasons why I like this approach:
· It’s a write once, use anywhere solution.
· It provides a pure markup solution that is easily understood by our designers.
· It has all the sexiness of attached behaviors.
(Btw, if you don’t think attached behaviors are sexy, then clearly you’re attaching the wrong behaviors!)
Quoting
"wpf-di...@googlegroups.com"
<wpf-di...@googlegroups.com>:
> I should point out, Dr.
WPF's solution is definitely more *elegant*, but an
> />>
>
Thumbs up. Publish away! :-D
You are the Code Project master, my friend! If you wait for me, it'll likely be another week. :-s
>>> *(Btw, if
you don't think attached behaviors are sexy, then clearly
>>>
you're attaching the wrong behaviors!)*
Hey Pavan,
Yes, this approach is really a way to turn the TVI, itself, into a CheckBox. The CheckBox just becomes a way to visualize the checked state and to support toggling via the mouse. (The attached property adds a mouse click handler, but that won't get called in this scenario because TVI has a class handler that marks the bubbled mousedown event as handled.)
In your scenario, you would have to decide if you really want the TVI, itself, to be so strongly associated with a single control in the TVI. Once you have more than one child control, this provides an odd user experience.
Moving focus programmatically is always a hard thing because you have to think about all the possible user expectations. I'm not sure you can solve it with a one-size-fits-all solution.
For example, if you have a TextBox in your TVI, the user may still expect the up and down arrow keys to move them between TVIs. If you need to support this, then you'd have to handle tunneled key events and move focus programmatically. And although you might want this behavior in one app, you might not want it in another app (where you might have a multiline text control).
If you really do want to move focus, then you are probably getting to the point where I'd recommend deriving your own TVI (and your own TreeView that uses your custom TVI as its item container). Attached event handlers only get called after class handlers, so there's only so far you can take them. You may find that you need to override behavior of the base TVI class.
If you have a simple sample that illustrates what you are doing, I can take a look, but again, I would hesitate to propose a generic solution because other scenarios will likely break the model.
You could definitely come up with a custom TreeView class (and TVI class) that provides configurable properties to help the user establish the correct navigation experience. Of course, at some point, such a solution starts to overcomplicate the matter. In many cases, the developer can get the right experience by simply changing the KeyboardNavigation attached properties on the ItemsControl.
Cheers,
-dw
I've worked with focusscopes so I can chime in here if its ok. I believe the only intrinsic focusscopes are the Window, Toolbar, and Menu. We used it in our ribbon as well. Basically a focusscope maintains its own focusedelement. I originally thought this was analogous to how a winforms containercontrol (i.e. usercontrol and form) had its own ActiveControl but its really not. The wpf framework makes some assumptions/behaviors based on the focusscope. The Toolbar and Menu are focusscopes so that they can have keyboard focus without losing/manipulating the real/logically focused element that is on your window. So if you have a menu open and close it, focus should really be moved back in the textbox or whatever you had focus in within the main window. There are several implications when you make something a focus scope. First, when something calls Keyboard.Focus(null) - which things like the Button do when clicked or more accurately when it loses capture and its not in the "main" focusscope - the framework tries to shift focus to the focused element of the "main" focusscope. I could be wording it slightly improperly but that's the basic gisc. Second, as routedcommands are bubbled up the element tree, when they hit a focusscope, the command manager reroutes the command to start at the focusedelement of the parent focusscope. This is how come a command on a button in a toolbar button or menu can get routed to the textbox that had focus on the window. So unless you're making something that is essentially a toolbar/menu type element, you really do not want to make the element a focus scope.
-Andrew
Andrew's description is quite concise and pretty much covers it. :-)
The only other place in the framework where FocusScope is used is on ElementHost (for obvious reasons).
FocusScope has been on my list of future blog topics for some time now, but it keeps getting pushed down because it is primarily relevant to control vendors. Unfortunately, my list is becoming quite long and I've only posted one article in the last couple of months. :(
Maybe my new community strategy will be to start sending interesting things to Josh! :-D
That's what I originally thought in making my original comparison to a winforms containercontrol and while its true that each focusscope maintains its own focused element (in the focusscope dp using focusmanager.focusedelement), as I mentioned there are subtle implications of making something a focusscope that really don't make it suitable (at least in my opinion) for general focus management use. So if you did make an element a focusscope and you had a button within that element, when you click it focus is going to shift out of your focusscope back to the root one. Likewise, if you had a toolbar that was on the main form (i.e. not within your element that's a focusscope) and you click on a button within that toolbar that had a command associated with it (without an explicit target element), the command is going to route up that toolbar and then to the focused element of the main window (since that is its focusscope's parent focusscope) and not get to the focused element of your focusscope. So basically the way its setup, it's really only useful if you're creating a commandsource host type control (i.e. toolbar/menu).