WPF - Combobox with a Prompt - wpf

I'm looking for a way to add a "SELECT An Item" to a combobox that does not have an item selected. This would be different than a selected item as a default.
I want the combobox to say "SELECT an Item" and be bound to one list for the possible selections and another model for the selected item.
I'd prefer a style that I can apply to multiple comboboxes and have a way to set the prompt. I've seen something similiar at http://marlongrech.wordpress.com/2008/03/09/input-prompt-support-in-wpf/, but the it does not work smoothly, requiring 2 clicks to get to the list.
Thanks!

You could use the adorner solution you linked to with a couple of changes, or you could do this with a style and converter.
Adorner solution
The adorner solution is more complex, but has a better interface and encapsulation. The changes you would need to make are straightforward but possibly difficult if you're not a WPF expert. They are:
Recognize ComboBox as another special case (like TextBox). Subscribe to its SelectedItemChanged, and update adorner visibility using SelectedItem==null.
Don't handle input events (HitTestVisible=False, Focusable=False, etc)
In this case, your ComboBox style will be very simple, just setting the attached property.
Style and converter
Doing it with a style and converter may be simpler for you. Here is the body of the converter:
object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value==null ? Visibility.Visible : Visibility.Hidden;
}
Your style will replace the default ComboBox style and contain a copy of the ControlTemplate from the theme, wrapped with something like this (or use an adorner):
<Style TargetType="{x:Class ComboBox}">
<Style.Setters>
<Setter Property="local:MyInputPromptClass.PromptText" Value="SELECT an item" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Class ComboBox}">
<Grid>
... existing XAML from theme ControlTemplate ...
<TextBlock
Text="{Binding local:MyInputPromptClass.PromptText, RelativeSource={RelativeSource TemplatedParent}}"
Visibility="{Binding SelectedItem, Converter={x:Static local:MyInputPromptClass.Converter}, RelativeSource={RelativeSource TemplatedParent}}"
HitTestVisible="False" Focusable="False"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
This solution is less satisfying than the other, since by copying the default ComboBox template from a theme you end up with an app that doesn't track the current Windows theme. It's possible to get around this using multiple ControlTemplates along with StaticResource and some tricky binding, but at that point I would recommend just using the adorner and attached property.

Related

WPF ComboBox: How to you utilise a generic ItemContainerStyle with binding

I want to utilise a generic style for my ComboBoxItem content and have the text content bound to different properties on my underlying class. So this is the best I can come up with but the bindings are hard coded. So for every class bound to a combobox using this ItemContainerStyle I'd have to implement a "MainText" and "SubText" property.
Question is, is there a way to have the binding soft coded so where the style referenced from a combobox I can specify which string properties of the underlying class are used.
<Style TargetType="{x:Type ComboBoxItem}" x:Key="ComboBoxItemStyleA1">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border x:Name="BB" Padding="8,3,8,3" Background="DarkGreen">
<StackPanel Margin="0">
<TextBlock Foreground="White" FontSize="16" Text="{Binding MainText}"/>
<TextBlock Foreground="White" FontSize="8" Text="{Binding SubText}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" TargetName="BB" Value="#FF256294"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And to use the style...
<ComboBox ItemsSource="{Binding Items}"
ItemContainerStyle="{StaticResource ComboBoxItemStyleA1}" />
Further to dowhilefor's answer (many many thanks - WPF is great but sure is a voyage of discovery)
I used a data template to define the cell look originally - and then wanted to use a comboboxitem based style with a control template defined where I could specify the onmouseover triggers. i.e. these were to change the background color etc.
Butj
a) I couldn't remove the Border section of the template above - the triggers are tied to it by targettype="BB". so I kind of wanted to get the trigger bound to the container such that the datatemplate would pick up the background from the template binding but not sure how to get this plumbed in.
b) I realised that even if I comment out the BB specific bindings on the triggers just to get it to run - the combobox doesn't find and use the DataTemplate I defined. Seems that defining the controltemplate in my comboboxitemstyle stops it picking up the datatemplate.
I hope I make sense here - bottom line is I just want a style that I can apply with triggers in that set the background color of my cobobox item. It should not know what the data is - i.e. be able to plug in a datatemplate that will (template ?) bind to this background color.
Many thanks for the very fast response.
btw I'm using ItemContainerStyle in conjuction with ItemTemplate so I can have a different representation in the dropdown to what appears in the combobox list
First of all don't use the ItemContainerStyle for that. To be more precise never have any Bindings to the datacontext inside an ItemContainerStyle, at least try not. Why? The Style is used for defining the appearance of a combobox item disregarding the content. If you want to define how the content should look like, you use a DataTemplate for that. There are multiple ways to tell the combobox where he can find a proper DataTemplate for the Data you supply. Checkout the property ItemTemplate, ItemTemplateSelector and search for implicit styles, to find out more about them.
So to your problem, create one ItemContainerStyle for you combobox (if you really have to anymore) which doesn't care about the object that will be put into. Now you still need to provide multiple DataTemplates each and everyone with the knowledge of the data object that you want to be templated. There is no way around it, there is no soft databinding. Just try to keep your templates small and simple. If for some reason you need the exact same template, but your properties are just named differently, why not use a wrapper item for the DataContext with the properties Caption, Description and you can decide in code how these properties are filled with your real data wrapped into this object.

Conditional loading of WPF Control with only XAML code

Suppose i have a complex UI. Depending on the available MODEL data, some of the controls make no sense. I would like to have an option to 'disable' them. By 'disable' I mean that I don't want their DataBinding to happen as their ViewModel is unsafe\ undefined.
In this post, it was suggested to use DataContentSelector.
I wonder if there's a different approach that doesn't use code outside of the xaml. For example, an implementation using VisualState, where the VisualState will set the content to some empty box if the data is empty.
Thanks
The fully MVVM-compliant way in which I have been achieving this is as follows:
Say you have a view model of type MainViewModel which is being displayed by a view of type MainView.
MainView needs to conditionally show a sub-view of type SecondaryView, depending on the value of some controlling property in MainViewModel: when the controlling property has a certain value, the view is to be shown, when the controlling property has another value, the view is not to be shown, and when it is not shown, we do not want it to just be hidden, we want it to not even exist.
Introduce a new view model of type SecondaryViewModel. It can even be an empty class, though you will probably find some useful functionality to put in it.
In your MainViewModel introduce property MySecondaryViewModel of type SecondaryViewModel. This will be the controlling property. Initially, let the value of this property be null.
In the XAML of MainView, define a ContentControl as follows:
<ContentControl Content="{Binding MySecondaryViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type myViewModels:SecondaryViewModel}">
<myViews:SecondaryView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
So, now, the nullness of the controlling property MySecondaryViewModel controls the existence of SecondaryView:
For as long as the value of the controlling property is null, the framework does not create any view to display it, and the ContentControl is empty. (It has no content.)
If you assign an instance of SecondaryViewModel to the property, the framework will create an instance of SecondaryView to show it.
If you later set the controlling property to null again, the instance of SecondaryView will be destroyed.
I use this trick: ( sorry for bad English)
First i bind my DataContext to my VM and add a Converter to it
<Grid DataContext={Binding myvm, Converter={StaticResource mySwitchOfConverter}}
and in my converter i have something like this :
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(Util.SwitchContext == true)
return value;
return null;
}
Util.SwitchContext is a static property witch i'll set and unset it in my code.
<UserControl.Resources>
...
<DataTemplate x:Key="someControl" ...>
<DataTemplate x:Key="somePlaceholderControl" ...>
</UserControl.Resources>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource somePlaceholderControl}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SomeTriggerProperty}" Value="SomeTriggerValue">
<Setter Property="ContentTemplate" Value="{StaticResource someControl}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

Difference between Label and TextBlock

According to the Windows Applications Development with Microsoft .NET 4 70-511 Training Kit
What is the difference between the Label control and TextBlock control since both are content controls and just displaying text?
TextBlock is not a control
Even though TextBlock lives in the System.Windows.Controls namespace, it is not a control. It derives directly from FrameworkElement. Label, on the other hand, derives from ContentControl. This means that Label can:
Be given a custom control template (via the Template property).
Display data other than just a string (via the Content property).
Apply a DataTemplate to its content (via the ContentTemplate property).
Do whatever else a ContentControl can do that a FrameworkElement cannot.
Label text is grayed out when disabled
Label supports access keys
Label is much heavier than TextBlock
Source
Some more interesting reads below
http://www.wpfwiki.com/WPF%20Q4.1.ashx
What is the difference between the WPF TextBlock element and Label control?
Labels usually support single line text output while the TextBlock is intended for multiline text display.
For example in wpf TextBlock has a property TextWrapping which enables multiline input; Label does not have this.
Label is ContentControl which means that you can set anything as a content for it. Absolutely anything including strings, numbers, dates, other controls, images, shapes, etc. TextBlock can handle only strings.
Although TextBlock and Label are both used to display text, they are quite different under the covers.
=> Label inherits from ContentControl, a base class that
enables the display of almost any UI imaginable.
=> TextBlock, on the other hand, inherits directly from FrameworkElement, thus missing out on the behavior that is common to all elements inheriting from Control.
The shallow inheritance hierarchy of TextBlock makes the control lighter weight than Label and better suited for simpler, noninteractive scenarios.
PS: However, if you want access keys to work or want a more flexible or graphical design, you’ll need to use Label.
Probably the most annoying feature of TextBlock is the implicit style lookup behavior, which is scoped to only to the closest DataTemplate. This is a default behavior for non Control xaml elements.
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Red"/>
</Style>
<Style TargetType="Label">
<Setter Property="Foreground" Value="Red"/>
</Style>
</StackPanel.Resources>
<ContentControl Content="Test">
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
<ContentControl Content="Test">
<ContentControl.ContentTemplate>
<DataTemplate>
<Label Content="{Binding}"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</StackPanel>
Yields a result of:
You can read more about it here.

Puzzle - Dynamically change data template control from another data template

I have a DataTemplate that contains an Expander with a border in the header. I want the header border to have round corners when collapsed and straight bottom corners when expanded. What would best practice be for achieving this (bonus points for code samples as I am new to XAML)?
This is the template that holds the expander:
<DataTemplate x:Key="A">
<StackPanel>
<Expander Name="ProjectExpander" Header="{Binding .}" HeaderTemplate="{StaticResource B}" >
<StackPanel>
<Border CornerRadius="0,0,2,2">
This is the expander datatemplate:
<DataTemplate x:Key="B">
<Border x:Name="ProjectExpanderHeader"
CornerRadius="{Binding local:ItemUserControl.ProjectHeaderBorderRadius, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}"
Background="{StaticResource ItemGradient}"
HorizontalAlignment="{Binding HorizontalAlignment,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}},
Mode=OneWayToSource}">
<local:ItemContentsUserControl Height="30"/>
</Border>
</DataTemplate>
Bind the CornerRadius property to the Expander.IsExpanded property and attach an IValueConverter that returns rounded corners when false and straight bottom corners when true. It's not the most elegant, but it will get the job done.
The other way to do this, if using MVVM, would be to expose a boolean property and bind it to the Expander.IsExpanded property. Then expose another property for the CornerRadius, which checks the boolean property and returns the appropriate values. This is definitely the "best practice" way to go about this.
Another way to do this is by editing the control template. The argument can be made that this is the best practice, though I'm not sure I'm ready to commit to that.
It's straightforward to do this if you have Expression Blend. An advantage of editing the control template is that it separates the behavior of the Expander from your data template, so that you can reuse it across different types of data. A disadvantage is that you end up embedding the properties of the header's Border in the control template, so you can't really change them for any individual instance of the control. (Other disadvantages: you have to have Expression Blend, and it produces a big bolus of XAML that you have to put in your resource dictionary.)
In Expression Blend, create an empty page and put an Expander on it. Right-click on the Expander and pick "Edit Template/Edit a Copy...". Give it a name like "ExpanderRoundedCorners".
This will add about 200 lines of XAML to Page.Resources, but most of this is used to create the graphics on the expand button. Switch to XAML view and search for the ToggleButton named "HeaderSite". This is the expand button. Note that its Content property is set to {TemplateBinding Header}. We'll want to fix that.
Delete the Content property, and add a child element to the ToggleButton like this:
<ToggleButton.Content>
<Border x:Name="HeaderBorder" BorderBrush="Red" BorderThickness="2">
<ContentPresenter Content="{TemplateBinding Header}"/>
</Border>
</ToggleButton.Content>
Now find the trigger that makes ExpandSite visible when the ToggleButton is pressed. Add this setter to it:
<Setter TargetName="HeaderBorder" Property="CornerRadius" Value="4"/>
That's it. Now every time you create an Expander with the ExpanderRoundedCorners style, the header content will be enclosed in a Border whose corners are rounded when the Expander is expanded.
You'll probably want to jigger around with this a little more when you've got it working. At the least, you'll want to remove the Border from the header template in your style, since it's now part of the control template.

How do I update a label that is in a ControlTemplate of a Toolbar in WPF?

I have a ControlTemplate that is made up of a ToolBarTray and a ToolBar. In my ToolBar, I have several buttons and then a label. I want to be able to update the label in my toolbar with something like "1 of 10"
My first thought is to programatically find the label and set it, but I'm reading that this should be done with Triggers. I am having a hard time understanding how to accomplish this. Any ideas?
<Style x:Key="DocViewerToolBarStyle" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<ToolBarTray... />
<ToolBar.../>
<Button../>
<Button..>
<Label x:Name="myStatusLabel" .. />
The purpose of a ControlTemplate is to define the look of a control. For your problem, I'm not sure if a control template is the right solution.
As Bryan also points out, you should bind the Content property of the Label to a property that is already present in your control. This should be done via TemplateBinding.
<Label x:Name="myStatusLabel" Content={TemplateBinding MyStatusLabelProperty} ../>
The property MyStatusLabelProperty then has to exist at your control class.
Usually, you would create your own UserControl that has a dependency property of the correct type (either object or string) that is named MyStatusLabelProperty.
I would set the label to the "Content" attribute of your control e.g.
<Label x:Name="myStatusLabel" Content="{TemplateBinding Content}"/>
Then you can set your label's text with your top level object's Content attribute.
I would create a view model which implements INotifyPropertyChanged interface and use DataTemplate to display it using something like this:
<DataTemplate DataType={x:Type viewmodel:MyToolBarViewModel}>
<Label Content={Binding CurrentPage} />
<Label Content={Binding TotalPages} ContentStringFormat="{}of {0}" />
</DataTemplate>
<ToolBar>
<ContentPresenter Content={Binding <PathtoViewModel>} />
</ToolBar>
With using bindings you don't have to explicitly update label's content. All you have to do is set property's value in view model and raise proper PropertyChanged event which causes the label to update its content.

Resources