usercontrol (?) for WPF reusable menu - c#

I am a beginner at WPF so please excuse me if this question is too simple :)
I have a listbox which I would like to filter by various filter conditions. This listbox I fill with instances of a particular type. Each filter condition is associated with one of the listbox items' properties. (They are like: this or that string property starts with string XXX.)
For this I would need a menu for each property from which users can select the filter conditions they want to filter the items with. Each property of the same type will have the very same set of menu items with the various filter conditions. (For strings: starts with, ends with... for ints: lower than, higher than, etc.)
The menus require some code behind too so I don't want to program these for each property separately.
My problem is that I don't know in what way could I program these. I cannot program them as UserControls because all what I need is MenuItems in a Menu. But I cannot program them as MenuItem derived classes because I would need the XAML for designing them for each type. Could I create a MenuItem derived class with a XAML somehow? Or do you have any other suggestions?

In WPF, we work with data elements whose public properties are data bound to the properties of various UI controls via DataTemplates. Please see the Data Templating Overview page on MSDN for the full story.
In order to do this, we develop custom classes that contain all of the necessary properties that we need to display and then we declare one or more DataTemplates that define the binding connections between the classes and the UI controls, or MenuItems in your case.
The benefit of this is that in order to display a Menu in the UI, you just need to data bind one of your custom menu class objects to a control in the UI and let the DataTemplate do the rest. So if you want to change the menu contents, you just need to change the data item that is data bound to the Menu.
So to answer your question directly, a Menu control would be most suitable for you to use, but you don't store the Menu properties in your code behind... you store the property values in your custom classes that will be data bound to the Menu control properties:
<Menu ItemsSource="{Binding CollectionOfYourCustomClassItems}" ... />
It is worth pointing out that you may need to set the child MenuItem properties in a Style and not a DataTemplate as usual (taken from the accepted answer to the WPF MenuItem : Mix databound items and static content question (which I recommend that you read) here on Stack Overflow):
<MenuItem Header="_Recent Files" ItemsSource="{Binding Commands,Mode=OneWay}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Path=ShortName}" />
<Setter Property="ToolTip" Value="{Binding Path=FileName}" />
<Setter Property="Command" Value="{Binding Path=OpenCommand}" />
<Setter Property="CommandParameter" Value="{Binding Path=OpenParameter}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSeparator}" Value="true">
<Setter Property="MenuItem.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Separator Style="{DynamicResource {x:Static MenuItem.SeparatorStyleKey}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
You will find many more tutorials and related questions regarding data binding to MenuItems online, so I won't go over everything again here. Please see the following article to start with:
Binding menus using HeirarchicalDataTemplates

Related

WPF Treeview is not Collapsing Rows Even with Visibility Collapsed [duplicate]

I've been trying to just hide items from a TreeView. I'm using a custom data type as source (called SettingsMenuItem) which inherits from FrameworkElement (currently FrameworkContentElement, because otherwise the TreeView renders them wrong).
My goal is by setting the VisibilityProperty of these FrameworkElements to either Collapsed or Visible that I'm able to hide certain items (including their children). I know that this can be done by deleting items from the source collection. But that's not what I want. It would mean that I have to mirror each collection in order to keep track of it's actual items, bind to each one in order to be notified about Visibility-changes and create a new collection each time one changes. A lot of overhead for this.
Right now I have no clue how I could accomplish that. I figure it's related to the ItemsGenerator, but I haven't seen any possibility to override it's behaviour. I thought TreeView would be able to detect Visibility, but obviously it doesn't. As alternative I thought of a custom TreeViewItem (maybe even TreeView if necessary) - but at this point the abstraction of this whole system overwhelms me. I don't know where to start and what is actually necessary to solve the problem.
Tips what I have to change or implement by myself would be more than enough. A complete solution would be nice.
You can do this using a data trigger bound to a property (e.g. "IsVisible") in you tree data nodes:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
While this technically answers your question I would be wary of actually doing it. User3690202's comment is correct, it's the sort of thing you would normally do via filtering in your view model.
For alternate solution using code behind xaml.cs:
To Remove a specific TreeViewItem from a TreeView which is created from a code behind.
TreeViewItem treeViewItem1 = new TreeViewItem
{
Visibility = Visibility.Collapsed,
};
use the code with TreeViewItem you want to hide in a if condition to hide specific TreeViewItem Header let say "Cars" and you want to hide it and use the code with if condition to hide "Cars" TreeViewItem.

Change the control property globally in wpf

I have a back button which is copied almost to all the Controls in my application.
I have set the styles and properties of the button on each individual control (usercontrol)
Now I want to change the text property of the button of all the control (usercontrol).
I don't want to go and change the property of each control.
Please help me setting a global property which sets the property in one place.
Since the style is common to all pages. Create the style without a key/name, just the target type would do.
<Style TargetType="{x:Type Button}">
Then do either of the following -
Add it to the App.XAML for visibility throughout the app
Better approach would be to define a resource dictionary file and import it, wherever you need it.
<Style TargetType="{x:Type Button}">
<Setter Property="Text" Value="{Binding text}" />
<Setter Property="...." Value="{Binding ....}"/>
</Style>
Add this to App.xaml file as you want it to be global style for all your user controls.

Add items at runtime to a databound context menu

I have a requirement to show a list of items in a context menu. In addition to this, I need to show the frequently used items (configurable by user) on the top, followed by a separator, and then the standard list of all the items. I know, I can add all the items to context menu at runtime but I want to explore different options too. The question is - is it possible to:
Bind the standard list in xaml and then add the frequently used items at runtime.
OR
Bind the context menu to two separate list
OR
Any other better option
Please note that I need to maintain two separate lists due to some technical reasons.
I am not showing any existing code because this question may be considered as a generic question and may apply to any control.
The second option is doable using a CompositeCollection, however the binding capabilities are a bit dimished (cannot use DataContext, ElementName or RelativeSource) in the CollectionContainer.Collection-Binding.
This answer of mine on another question shows two ways in which you can bind. If you cannot make do with those restrictions you will have to create the composite collection in code-behind.
I would manage my menus in the ViewModels, not in the XAML. My ViewModel would be responsible for returning a collection that combines both the standard Menu collection, and the custom UserCollection.
Usually I separate the items with a null value, and use a DataTrigger to draw the template as a Separator if the item is null.
Something like this:
myMenu.AddRange(UserMenu);
myMenu.Add(null);
myMenu.AddRange(StandardMenu);
and the XAML...
<ContextMenu ItemsSource="{Binding MyMenu}">
<ContextMenu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Template"
Value="{StaticResource MyMenuItemTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding }" Value="{x:Null}">
<Setter Property="Template"
Value="{StaticResource MySeparatorTemplate}" />
</DataTrigger>
</Style.Resources>
</Style>
</ContextMenu.Resources>
</ContextMenu>

Inheriting ComboBox in WPF using C#

I am deriving from Combobox to add some additional functionality, such as a checkbox.
The issue is, even with a simple implementation the Items.Add method does not work.
For example, here is the XAML:
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<ComboBox>
</ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The ComboBox is visible, but no information is added when I call the Items.Add method. What do I need to implement from the ComboBox class to achieve this? Do I need to do something with the popup? Add a Textblock?
That doesn't look to me like you're deriving from ComboBox... It looks to me like you're putting a ComboBox inside the ControlTemplate of your custom control.
If you are also deriving your custom control from ComboBox and calling Items.Add on your custom control, then you've basically got two lists of data (one for your custom control and one for the combobox in your controltemplate) and they are not linked in any way.
I'd suggest popping open Expression Blend and taking a look at the control template for a default ComboBox. If you want to derive from ComboBox you can then modify that controltemplate to suit your needs.

Focus-dependent text change for TextBoxes in WPF

I'm writing an application in WPF using the MVVM-pattern and will really often use TextBoxes.
I don't want to use labels for the user to know user what the text box is for, i.e. I don't want something like this:
<TextBlock> Name: </TextBlock>
<TextBox />
Instead, I would like the TextBox to contain its own label. Statically, you would express it like this:
<TextBox>Name</TextBox>
If the cursor is displayed in the textbox, i.e. the TextBox gains focus, I want the description text to disappear. If the TextBox is left empty and it loses the focus, the description text should be shown again. It's similar to the search textbox of StackOverflow or the one of Firefox. (please tell me if your not sure what I mean).
One TextBox's label may change at runtime, dependending on e.g. a ComboBox's selected element or a value in my ViewModel. (It's like in Firefox's search TextBox, if you select google from the search engins' menu, the TextBox's label changes to "Google", if you select "Yahoo" its set to "Yahoo"). Thus I want to be able to bind the label's content.
Consider that I may already have a Binding on the Text-Property of the TextBox.
How can implement such a behaviour and make it reusable for any of my TextBox's? Code is welcome but not needed; a description of what to do is enough.
Thank you in advance.
Here is a style I think is exactly what you are looking for, and it's pure XAML.
<Style x:Key="WatermarkTextBox" TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Border x:Name="BorderBase" Background="White" BorderThickness="1.4,1.4,1,1" BorderBrush="Silver">
<Label x:Name="TextPrompt"
Content="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Tag}"
Background="{TemplateBinding Background}" Visibility="Collapsed"
Focusable="False" Foreground="Silver"/>
</Border>
<ScrollViewer Margin="0" x:Name="PART_ContentHost" Foreground="Black"/>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="False"/>
<Condition Property="Text" Value=""/>
</MultiTrigger.Conditions>
<Setter Property="Visibility" TargetName="TextPrompt" Value="Visible"/>
</MultiTrigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="BorderBrush" TargetName="BorderBase" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="DimGray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage is:
<TextBox Style="{StaticResource WatermarkTextBox}" Tag="Full Name"/>
where Tag is the help message you want to show.
You could clean up this style for your own use, but the most important part is the which controls hiding/showing the helper text.
It's worth noting as well, there is already a DependencyObject available for storing the helper text, so you don't need to create your own with this method.
FrameworkElement.Tag is available for holding arbitrary information about this element. That's why we set the Tag property:
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.tag.aspx
You could derive from TextBox and implement your behaviour. The TextBox offers the events GotFocus/LostFocus (or the methods OnGotFocus/OnLostFocus respectively) which should help. You also should consider offering a new DepedencyProperty, so you can define the default text in xaml and bind it to other controls/resources etc.
To amplify on my suggestion about using an adorner.
An Adorner is basically an element, rendered on its own layer, that appears over/around another element. For instance, if you implement validation in a binding, the red box that decorates an invalid control is an adorner - it's not part of the control, and it can be (and is) applied to all kinds of controls. See the Adorners section of the WPF docs for a simple but clear example.
I thought of an Adorner for a couple of reasons. The principal one is that the behavior you're describing might not necessarily be confined to a TextBox. You might, for instance, want to have a ComboBox exhibit the same behavior. Implementing an Adorner would give you a consistent way to implement this functionality across multiple controls (though it doesn't make sense in, say, a CheckBox or a ProgressBar). A second is that you wouldn't have to do anything to the underlying control more elaborate than implementing triggers to display and hide the Adorner in response to focus events. Adorners are a bit of a pain in the butt to implement, but it's worth knowing how to.
All that said, I like mattjf's answer a lot more than I like mine. The only disadvantages I see with that approach are 1) It only works with the TextBox; you need to implemnent a new version of the style every time you want to use the approach on another control, 2) I may just be engaging in magical thinking, but every time I ever used the Tag property in WinForms it told me (once I learned to listen) that I was building something fragile. I don't know for sure that this is also true in WPF, but I bet it is.
My comment on using the bound Text property probably needs amplification. If you use the Text property to store the field label, then you've got a number of hard-to-solve problems. First, since it's a bound property, changing its value in the TextBox will change it in the source. So now your source needs to know a lot of information about the state of the UI - does the control currently have the focus? If the value of the Text property is Foo, does that mean that the label is Foo, or the user typed in Foo? There are probably ways that you can manage this, but the best way to manage it is to not have to.
(One other problem with this paradigm: What should be the behavior be if the user wants the value of the TextBox to be the empty string?)

Categories