Rob Relyea has a customer (I wonder if it’s Kevin’s Mom?) who is looking for an implementation of this Views menu that is used in Vista’s Explorer window.
Since I haven’t seen anyone respond, I put together a little app to demonstrate how you might do this in WPF. (Yes, I’ll use any excuse to knock off another letter of the alphabet in this series!)
This really does qualify for the ItemsControl series. Not only is a ContextMenu an ItemsControl, but I also demonstrate how to use the menu to control another ItemsControl… a ListView. In total, this sample demonstrates binding to a collection (‘C’ is for Collection), creating a dynamic item template (‘D’ is for DataTemplate), and using triggers to adjust properties on a custom items panel (‘P’ is for Panel).
One would typically implement a user control for a menu like this because the control is really a collection of several other controls (in this case, a slider, several menu items, and a popup) with well-defined, static visuals. However, because I wanted several behaviors that come for free with a context menu, I just took a shortcut and derived directly from ContextMenu. I used the control’s template to define the visuals. Make no mistake… this is not a "custom control" in the WPF lookless sense of the term. The implementation is very much tied to the visual elements in the template.
Disclaimer: This is just an example of how such a menu could be constructed. I readily admit that I didn’t spend much time on it and there are lots of improvements that could be made. Also, there are some interesting behaviors within the Views menu in Vista. For example, the menu is always opened such that the slider’s thumb is directly under the mouse. I implemented this feature and some of the others that I noticed, but I didn’t spend a lot of time trying to precisely imitate all the behaviors. There’s certainly still some cleaning up to be done if you require pixel perfection in sizing, iconography, etc.
Thanks Dr. WPF!
Would you believe it was a dev on the Windows Explorer team? 🙂 Ok, not really.
Thanks, Rob
Interesting sample. I’m sure this was a quick implementation but I have a couple of questions/comments.
You shouldn’t use interop to get the mouse position. This basically means it won’t work in xbap. Also, you’re assuming that the logical pixels equal screen pixels – technically you should be converting the screen pixels to logical pixels. The better way would be to simply set the Placement to MousePoint and then set the (Vertical|Horizontal)Offset(s).
e.g.
Placement = PlacementMode.MousePoint;
VerticalOffset = -offset.Y;
HorizontalOffset = -offset.X;
Also, why did you write a routine to walk down into the thumb? The track is a template part for the slider so if you really needed to get to it, you could get that part (yes I know you’d have to walk down into it to get that) and then access the public Thumb property of the track. That being said, this seems like an error prone approach – what if the slider is restyled (e.g. because the theme changes) but your element isn’t? Then the thumb you’re hooking won’t be the one in use. Wouldn’t it be better to register a class level handler for the thumb class’s events that you’re interested in? You also run the risk of rooting that element – you hook its events but never unhook.
-Andrew
Hey Andrew. Good comments, all of them.
> You shouldn’t use interop to get the mouse position.
> This basically means it won’t work in xbap.
True, but it already won’t run in partial trust with the designed visual appearance because it is popup-based. I meant to mention that in the post, but somewhere along the way, I got sidetracked.
You’re absolutely right on conversion to device-independent pixels… that was just me being lazy. I’ll probably update the sample with MousePoint placement, just because now that you’ve mentioned it, it’s going to bug me .
> Also, why did you write a routine to walk down into the thumb? . . .
This is why I put the caveat in the article about not thinking of this as a lookless control. I approached this as a user control. With that assumption, I decided it was reasonable to assume there would be a thumb in my tree. Since I prefer to use a Slider style that does not contain a Track (cuz I don’t like that whole construct anymore than I like the named parts contstruct), I explicitly look for a thumb.
I should clarify that I don’t like the PART_ construct for custom controls. This type of user control scenario represents one of the few times when I think looking up template elements by name makes sense.
I’ve never seen a Slider style that doesn’t contain a Thumb, but I suppose it’s possible. If someone needs to support such a style, they can modify the control to accomodate it.
> You also run the risk of rooting that element.
Again, it’s a user control, not a lookless control… it is hard-bound to the visuals. It’s just the same as if I had a xaml-based front end with a thumb in it and I set the handlers on it in code.
I totally agree that in a lookless control, you should have DetachFromVisualTree and AttachToVisualTree routines that get invoked in OnApplyTemplate, but here I’m taking liberties by assuming that dynamic template switching on this control is out of scope for its usage scenarios. (Having said that, I’ll probably make that change too while I’m in the code, since its a good pattern to encourage.)
And honestly, if you switch themes on this app (which is the only thing that would cause the leaked thumb that you describe), the control is not going to look right… it is specifically designed for the Vista (Aero) theme. If it were more than just a “sample”, I’d hardcode the styles for all elements in the menu, including MenuItem, Slider, Separator, etc, so it could stand up to a theme switch.
Thanks again for the great feedback (and extra work). I’ve now spent more time responding to you than I did on the entire sample! 😉 (Why do I have a blog, again?)
-dw
Thanks for taking the time to respond to my comments. Popups do work in xbaps – they’re just not top level windows. 🙂
Sorry if I made more work for you. I do appreciate the time that people take to blog, etc. and share information/code; I’m struggling to find more time for it myself. I have a habit of reviewing code while I’m reading it. What can I say – I write custom controls (wpf & winforms) for a living so its just ingrained. I just wanted to make sure that people who come across the code understand the limitations. I think there are lots of people who find code and use it as is within their applications without understanding the limitations/issues or how it really works and then they’re stuck when problems come up with it.
Thanks again for a cool project.
Thanks,
Andrew
p.s. Can you make the comment textarea on your site larger? 🙂 Its hard typing more than a couple of sentences in the little area.
> Popups do work in xbaps…
Right. But for this specific control, my goal was to provide the Vista experience. Without a top-level window with per-pixel alpha, the ContextMenu based experience would be the wrong approach for an xbap. If I were designing a solution for this scenario within an xbap, I would probably derive directly from UserControl and add my control to an overlay layer (or perhaps even use an Adorner-based approach). At least then I could have alpha blending with the surrounding UI elements.
For what it’s worth (not really much, I suppose), I was actually involved in the early decision to allow this limited support for popups in the partial trust sandbox. The team was also looking at providing a native dialog class for use within xbaps (but I don’t think that ever saw the light of day… which was probably the right decision, in retrospect).
> Sorry if I made more work for you.
Not at all. Now I know where to go when I need a code review! 🙂
I actually just uploaded an updated sample to address the deficiencies you noted. The p/invoke call has been removed so it will run in partial trust (although, again, not the way I would go for that scenario).
I added calls to attach and detach the control from the visual tree, although they are purely there to represent a best practice. As I mentioned before, the control is not designed for dynamic template switching.
Finally, I addressed the theming issue by merging the Aero theme directly into the control template’s resources. This ensures that even if you run under another theme, you will get the aero styles. Now a dynamic switch of the system theme won’t impact the visuals in the least. It should go without saying that I am not advocating this approach for control vendors! Since I was already linking with the Aero theme’s assembly to get the chrome for my context menu, it was a quick and easy way to address the concerns you raised about theme switching without extracting every Aero style for elements in my visual tree.
Cheers,
-dw
Thanks KAE. In an effort to be a good world citizen, I’ve added the culture to the Parse() call. 🙂
DoubleComparisonConverter.Convert contains a call to double.Parse without specifying IFormatProvider.
That results in a FormatException for us "decimal comma" guys.
Adding ‘culture’ as the second argument to Parse should do the trick.
Thanks for the really slick menu example! I’ve been experimenting with the sample code and there are a couple things I don’t understand completely.
1) I’d like to be able to set the default view style in XAML. I’ve toyed with the code, but not figured out a solution.
In the Window1.xaml, the “ViewsMenu” is specified with a “CurrentView” value, but whatever value I set there will not affect what Isee in the designer or what I see when I run the sample app — but I wish it would.
Changing the KnownView value used as the “defaultValue” param in the CurrentViewProperty of the ViewsContextMenu class, will change the default I see at run time, but not what I see in the designer.
2) I noticed that the icon for the current view doesn’t show up on first run — I have to change the item layout style once or twice before the icon shows up to the left of “Views”. However, if I set the default value to (KnownView)KnownView.Details in the CurrentViewProperty of the ViewsContextMenu, I do see the icon at first run.
3) In Generic.xaml, the icons are referenced with relative paths. However, I moved xaml from Window1.xaml to a UserControl called “ListViewsView” in a WPF User Control Library. When I reference the library project from a different wpf app project (in the same solution), the icons won’t show up. The icons show up if I prefix their paths with: /NamespaceOfUserControl;component/, as if I’m referencing them from a different assembly, but I’m not. I’m referencing them within the UserControlLibrary, and they are contained in the UserControlLibrary. The App project is referencing them by reference to the UserControlLibrary, but I wouldn’t expect to need the “component” path, as I stated above, in this situation.
Hi Jeremy,
Items 1 and 2 don’t repro for me in the sample that I posted. I’m not sure what is happening in your app.
I should note that I haven’t actually tried opening any of these markup files in the designer, so I have no idea what the design-time experience is… sorry… I’m not a designer… all of my markup editing occurs in the “Source Code (Text) Editor”, which is my default editor for XAML files in VS. The runtime issues that you describe do not repro for me.
I don’t have a handy way to test Item 3. I wouldn’t think that the fully qualified path would be required either, but I often use it just to be more precise (although I didn’t in this sample).
Cheers,
-dw
Thanks a lot!! This was a real (life || time)saver!
But I have a question…
I added a click handler on the listview named "views". How can I get the specific item I clicked (aka the listviewitem) in code?
The basic approach is to look at the original source of the routed event (e.OriginalSource). If you cast it to a FrameworkElement (most likely your template only contains hittable framework elements, so this should be safe) and then look at its DataContext, you will know which item was clicked. Then you can use this along with the ContainerFromItem method on the ListView’s ItemContainerGenerator to get the ListViewItem. Another option is to simply walk ancestors using the GetAncestorByType() routine included in the ‘G’ article.
Hope this helps!
-dw
Hello Again! Sorry for being lazy in my previous post! 🙂
I have another question for you, which is straightly binded to my lack of WPF experience…
When I set the CurrentView to Icons (Medium+) the ListViewItem gets the width of the Textblock.Text, which messes up with the "proper" alignment of the list (The Monk way)..
I tried changing the TextWrap value of the Textblock but it wouldn’t obey… I’m trying to make a file explorer, so the exact behaviour of the Windows Explorer would be nice! (you know, the one with the 2-line name and the "…").
Any ideas?
Hi again M1ke,
You can get the desired effect by setting MaxWidth and MaxHeight on the TextBlock in addition to TextWrapping (Wrap) and TextTrimming (WordEllipsis). Try setting MaxHeight to 40 and then bind the MaxWidth property to the ActualWidth property of the Image element. You will probably also want to use the existing DataTrigger in the template to set the TextAlignment property to Left when the scale is less than 2.5.
Hope this all makes sense. You’ll have to tweak the template to meet your actual needs, but the above approach should give you the general idea.
Cheers,
-dw
Hi Dr,
nice sample. But I have 2 problems with it:
– performance: because you use StackPanels (that support no virtualization) performance is very bad for larger numbers of items. Any suggestions to get vurtualization back?
– selection: when you select items using keyboard every view selections like the list view (so if you select from left to right everything below the current item will be selected). Any suggestions to solve this?
Thanks,
Frank
My sample actually uses a WrapPanel as the items host for most views. If you want virtualization, you can implement a virtualizing wrap panel (or find a third-party virtualizing wrap panel) to use as the items host. Yes, you would want to do that for a scalable solution. I will cover virtualization in a future episode of this series. In the meantime, there are other blogs that cover how to implement a virtualizing panel (like here and here).
For me, the selection works pretty much the same as in Vista (except maybe the small icons view, where I intentionally switch to vertical wrapping). It should be a familiar keyboard experience to most users. Shift+Arrows can be used to extend selection and Ctrl+Arrows along with Ctrl+Space can be used to individually select items.
Cheers,
-dw
This was an inspiring sample. Great.
Hello Dr WPF,
Great series!
I was trying to implement a similar concept of selecting views. I have one additional view, TreeView. In this i take my flat observable collection of items and show it in a treeview. To show my collections in a treeview, i add group descriptions to my collection View.
For example in your case:
Tree might look like Gender->Age->Name
My problem is once i apply the group description. On Clicking TreeViewItem I do not know which Character item it is? Can you please guide me how to do this.
Thanks
Rohit
Cool.. thanks to Dr. WPF
Rohit,
The easiest way to get at the Character is to look at the DataContext of the framework element that was clicked. If that is not working or if I am misunderstanding the issue, please send me a repro offline.
Thanks,
-dw
Cool but i need more tutorial of wpf with c sharp.
thanks