Accessing a static object from Style/template in Generic.xml? - wpf

I have a double called LoadAnimAngle which simply holds the angle of a spinning loading icon, which gets rotated over time. This variable is defined in my MainViewModel class. I'm using this same variable across all places that has a spinning loading icon.
I need it inside a custom control that is defined in Generic.xml with a style/template. Here is the part where I'm binding to LoadAnimAngle:
<v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
<v:ColoredImage.RenderTransform>
<RotateTransform Angle="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}}"/> //here is the error
</v:ColoredImage.RenderTransform>
</v:ColoredImage>
The custom control has a property that is binding to my instance of MainViewModel, like so:
public MainViewModel MainViewModel { get { return MainViewModel.instance; } }
Inside the constructor of MainViewModel I simply set:
instance = this;
The problem is that Generic.xml gets loaded before my MainViewModel class, causing the instance to be null for the frame before the graphics have loaded, after everything is done loaded, everything works. How could I resolve this problem?
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=MainViewModel.LoadAnimAngle; DataItem=null; target element is 'RotateTransform' (HashCode=66939890); target property is 'Angle' (type 'Double')
Notice: I do understand that the error is harmless and does not effect anything for the end user, however seeing that error every time I debug causes me emotional pain.
I need to somehow load MainViewModel before Generic, OR, tell xaml to not try to get the data from LoadAnimAngle until MainViewModel != null.
EDIT
I get the same error after I made changes so that I do not directly bind to the instance of MainViewModel. So I think my evaluation of the case of the problem is wrong.
I added
public double LoadAnimAngle
{
get
{
if (MainViewModel.instance != null)
{
return MainViewModel.instance.LoadAnimAngle;
}
return 0;
}
}
to the view model (instead of return MainViewModel.instance)
Then I changed the binding to:
Angle="{Binding Path=LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"
I get the same error:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=LoadAnimAngle; DataItem=null; target element is 'RotateTransform' (HashCode=21529561); target property is 'Angle' (type 'Double')
If the problem is not that the MainViewModel.instance is NULL, then what is it that causes the problem? I have problems decoding the language in the error message. What exactly is wrong and why?
EDIT 2
Relevant context (?)
<Style TargetType = "{x:Type v:ComPortButton}" >
<Setter Property = "Background" Value = "{StaticResource Milky}"/>
<Setter Property = "ColorPalette" Value = "{StaticResource MilkyPalette}"/>
<Setter Property = "Foreground" Value = "{StaticResource Black}"/>
<Setter Property = "BorderColor" Value = "{StaticResource Milky}"/>
<Setter Property="IsBasicTextButton" Value="False"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type v:ComPortButton}">
<Grid>
<Grid Visibility="{Binding Path=IsBasicTextButton, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource InverseBooleanToVisibility}}">
<Border BorderBrush="{TemplateBinding BorderColor}" Background="{TemplateBinding Background}" Width="128" Height="140" BorderThickness="1"/>
//REMOVED IREELEVANT CODE
<v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
<v:ColoredImage.RenderTransform>
<RotateTransform Angle="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}}"/> //here is the error
</v:ColoredImage.RenderTransform>
</v:ColoredImage>
</Grid>
//REMOVED IRRELEVANT CONTROL
</Grid>
//REMOVED IRRELEVANT CONTEXT MENU
</Grid>
//REMOVED IRRELEVANT TRIGGERS
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
EDIT 3
The source of the error seems to be completely different from I first thought. The error seems to have something to do with RenderTransform, because I can access the property without errors from other places.
Like this:
// NO ERROR FOR TEXT BLOCK
<TextBlock Text="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
<v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
<v:ColoredImage.RenderTransform>
// ERROR FOR ROTATETRANSFORM
<RotateTransform Angle="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
</v:ColoredImage.RenderTransform>
</v:ColoredImage>
But I also get the error when I do not reference MainViewModel. I created a new property like this:
public double LoadAnimAngle
{
get
{
return 0;
}
}
Then I used it in the Template like this:
<v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
<v:ColoredImage.RenderTransform>
<RotateTransform Angle="{Binding LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
</v:ColoredImage.RenderTransform>
</v:ColoredImage>
But i get the EXACT same error!
So, the property works, everything works. It's just that RenderTransform is like outside of the VisualTree for the first frame when it is instantiated? Or something like that, i guess? Something different is happening in RenderTransform that makes it so it doesnt like my binding.
And i probably wasnt clear about the structure.
ComPortButton is a Custom Control (.cs file with Template/Style in Generic.xml).
ComPortButton uses ComPortVM as it's DataContext.
I want to access the spinning value globally, different controls, different windows, different everything, globally.
I have a MainViewModel in which i currently store the value, since it gives global access, since it
EDIT 4
Solved it and posted the solution below

After i figured it out that it was RenderTransform that was the problem and not anything else it was easy to find solutions online, seems that many people have had the same problem.
Here is the Thread that helped me solve it
The problem had something to do with VisualTree, that RenderTransform in the Template isnt hooked up to the VisualTree before the entire Control is loaded. Or something like that.
When binding like this to RotateTransform:
<v:ColoredImage.RenderTransform>
<RotateTransform Angle="{Binding LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
</v:ColoredImage.RenderTransform>
The problem occurs. But for some reason that i did not understand, you can get rif of the error by binding to RenderTransform instead. But for that you need a Converter.
[ValueConversion(typeof(double), typeof(RotateTransform))]
public class AngleToTransform : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return new RotateTransform((double)value);
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then use the converter like so:
<v:ColoredImage RenderTransform="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AngleToTransform}}"/>

Your control must be independent from the concrete view model type. Instead, bind internals of the control to dependency properties on this control. Then let the external view model bind to this properties (or set them locally).
This way you remove the tight coupling between the control and the DataContext, which drastically simplifies the implementation of the control. It also allows the control to be reused with any DataContext (view model).
ComPortButton.cs
class ComPortButton : Control
{
public double Angle
{
get => (double)GetValue(AngleProperty);
set => SetValue(AnglePropertyKey, value);
}
protected static readonly DependencyProperty AnglePropertyKey = DependencyProperty.RegisterReadOnly(
"Angle",
typeof(double),
typeof(ComPortButton),
new PropertyMetadata(default));
public static readonly DependencyProperty AngleProperty = AnglePropertyKey..DependencyProperty;
public double ProgressPercentage
{
get => (double)GetValue(ProgressPercentageProperty);
set => SetValue(ProgressPercentageProperty, value);
}
public static readonly DependencyProperty ProgressPercentageProperty = DependencyProperty.Register(
"ProgressPercentage",
typeof(double),
typeof(ComPortButton),
new PropertyMetadata(default(double), OnProgressPercentageChanged));
private static void OnProgressPercentageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
double percentage = (double)e.NewValue / 100;
// Enforce an angle between 0°-360°
this.Angle = Math.Max(0, Math.Min(360, 360 * percentage));
}
}
Generic.xaml
<Style TargetType="ComPortButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type v:ComPortButton}">
<v:ColoredImage>
<v:ColoredImage.RenderTransform>
<RotateTransform Angle="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Angle}" />
</v:ColoredImage.RenderTransform>
</v:ColoredImage>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage example
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<ComPortButton ProgressPercentage="{Binding ProgressPercentageValue}" />
</Window>

Related

ControlTemplate LayoutTransform Binding System.Windows.Data Error 2 or 4

I'm creating a custom UserControl which will act as a container, showing a self-sized watermark behind its Content.
The relevant part of the ControlTemplate (I'll give you the full thing below) is
<TextBlock
Text="{TemplateBinding WatermarkText}"
Foreground="{TemplateBinding WatermarkBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold">
<TextBlock.LayoutTransform>
<RotateTransform Angle="{Binding WatermarkAngle,RelativeSource={RelativeSource AncestorType={x:Type local:WatermarkUserControl}}}"/>
</TextBlock.LayoutTransform>
</TextBlock>
My UserControl has dependency properties for WatermarkText, WatermarkBrush, WatermarkAngle and WatermarkVisibility (I'll include that below). Notice that the TemplateBindings for WatermarkText, WatermarkBrush and WatermarkVisibility all work fine.
Using TemplateBinding for WatermarkAngle didn't work, because TemplateBinding is a lightweight "binding", so it doesn't support inheritance context. The WatermarkAngle binding that I ended up with (above) actually works; and yet a binding error is reported:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'RelativeSource FindAncestor,
AncestorType='[redacted namespace].WatermarkUserControl', AncestorLevel='1''.
BindingExpression:Path=WatermarkAngle; DataItem=null; target element
is 'RotateTransform' (HashCode=59772470); target property is 'Angle'
(type 'Double')
So is there a way of doing this better, which avoids the error being reported? And why is it reporting an error with the binding, given that the binding is actually working? (If I change the value, it reflects that.)
That concludes the question. Here are all the parts you need, to satisfy MCVE:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
public class WatermarkUserControl : UserControl
{
public static readonly DependencyProperty WatermarkTextProperty =
DependencyProperty.Register(nameof(WatermarkText), typeof(string), typeof(WatermarkUserControl), new PropertyMetadata("Watermark"));
public static readonly DependencyProperty WatermarkBrushProperty =
DependencyProperty.Register(nameof(WatermarkBrush), typeof(Brush), typeof(WatermarkUserControl), new PropertyMetadata(new SolidColorBrush(Colors.LightGray)));
public static readonly DependencyProperty WatermarkAngleProperty =
DependencyProperty.Register(nameof(WatermarkAngle), typeof(double), typeof(WatermarkUserControl), new PropertyMetadata(0d));
public static readonly DependencyProperty WatermarkVisibilityProperty =
DependencyProperty.Register(nameof(WatermarkVisibility), typeof(Visibility), typeof(WatermarkUserControl), new PropertyMetadata(Visibility.Visible));
public string WatermarkText
{
get { return (string)GetValue(WatermarkTextProperty); }
set { SetValue(WatermarkTextProperty, value); }
}
public Brush WatermarkBrush
{
get { return (Brush)GetValue(WatermarkBrushProperty); }
set { SetValue(WatermarkBrushProperty, value); }
}
public double WatermarkAngle
{
get { return (double)GetValue(WatermarkAngleProperty); }
set { SetValue(WatermarkAngleProperty, value); }
}
public Visibility WatermarkVisibility
{
get { return (Visibility)GetValue(WatermarkVisibilityProperty); }
set { SetValue(WatermarkVisibilityProperty, value); }
}
}
ResourceDictionary:
<Style x:Key="WatermarkUserControlBaseStyle" TargetType="local:WatermarkUserControl">
<Setter Property="Padding" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:WatermarkUserControl">
<Grid>
<local:NoSizeDecorator Visibility="{TemplateBinding WatermarkVisibility}">
<Viewbox>
<TextBlock
Text="{TemplateBinding WatermarkText}"
Foreground="{TemplateBinding WatermarkBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold">
<TextBlock.LayoutTransform>
<RotateTransform Angle="{Binding WatermarkAngle,RelativeSource={RelativeSource AncestorType={x:Type local:WatermarkUserControl}}}"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Viewbox>
</local:NoSizeDecorator>
<ContentPresenter Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DraftWatermarkStyle" TargetType="local:WatermarkUserControl" BasedOn="{StaticResource WatermarkUserControlBaseStyle}">
<Setter Property="WatermarkText" Value="DRAFT"/>
<Setter Property="WatermarkBrush" Value="LightPink"/>
<Setter Property="WatermarkAngle" Value="-20"/>
</Style>
NoSizeDecorator is defined here.
Example of use:
<local:WatermarkUserControl
Style="{StaticResource DraftWatermarkStyle}"
WatermarkVisibility="True">
<StackPanel>
<Label Content="Mr Smith"/>
<Label Content="1 High Street"/>
<Label Content="London"/>
</StackPanel>
</local:WatermarkUserControl>
I've worked out a way of doing it. It has made the error go away, and it still seems to work, and I haven't spotted any side effects yet.
I declared the RotateTransform as a local resource inside the Viewbox in the ControlTemplate, and then bound that as a StaticResource to the LayoutTransform property of the TextBlock. So the new version of the Viewbox from the question looks like this:
<Viewbox>
<Viewbox.Resources>
<RotateTransform x:Key="RotateWatermarkTransform" Angle="{Binding WatermarkAngle,RelativeSource={RelativeSource TemplatedParent}}"/>
</Viewbox.Resources>
<TextBlock
Text="{TemplateBinding WatermarkText}"
Foreground="{TemplateBinding WatermarkBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
LayoutTransform="{StaticResource RotateWatermarkTransform}"/>
</Viewbox>

WPF binding to ViewModel property from the DataTemplate Style

I am trying to bind ForeGround color of all TextBlock items to a ViewModel property. The TextBlock elements locate under a Grid that itself is defined under DataTemplate. This whole code is defined under a UserControl.
I am trying to use RelativeSource binding to find the UserControl's DataContext and get the property I need.
XAML:
<my:MapControl>
<my:MapControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="SomeTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="TextElement.Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.TextColor}" />
</Style>
</Grid.Style>
<TextBlock Grid.Column="0" />
<TextBlock Grid.Column="1" />
</Grid>
</DataTemplate>
</ResourceDictionary>
</my:MapControl.Resources>
</my:MapControl>
ViewModel:
public class MapViewModel
{
public virtual string TextColor
{
get { return _textColor; }
set
{
_textColor = value;
this.RaisePropertyChanged("TextColor");
}
}
private string _textColor = "Black";
}
The above binding doesn't work. If I change the Value binding to a hard-coded value, like "Red" for example, the Foreground color on those TextBlocks are showing correctly.
How to get the binding to work with this setup?
Analysis
It seems the root cause — binding to an instance of the string type instead of an instance of the Brush type.
Some of the possible solutions:
Change the type of the TextColor property of the MapViewModel class to from the string type to the SolidColorBrush type and update the implementation of the MapViewModel class appropriately.
Create custom implementation of the IValueConverter interface which takes the string as the input and outputs an instance of the SolidColorBrush type.
What version of .NET are you using? Works fine with 4.5 but IIRC it didn't with earlier versions and you had to declare a solidcolorbrush explicitly:
<Style TargetType="Grid">
<Setter Property="TextElement.Foreground">
<Setter.Value>
<SolidColorBrush Color="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.TextColor}" />
</Setter.Value>
</Setter>
</Style>
Whatever you do don't create a brush or any other UI resources in your viewmodel, it's a violation of MVVM.

WPF ListView: Icons view shown in a single column

I'm experiencing a weird problem...
What I'm trying to do is quite standard, I guess: aloowing the user to switch between Grid
and Icon modes in my ListView.
All is going well, but... The Icon view, instead of showing the items in wrapping rows, shows them in a single column, with each item occupying the whole width of the view. And I can't put my finger on what exactly is wrong... :-(
(I haven't earned enough XP on this forum yet, and it won't allow me to post images; I'll give the links to the screenshots instead)
What I want: http://i.stack.imgur.com/jYhVx.png
What I have: http://i.stack.imgur.com/PeAae.png
Here's the IconView style definition (in Themes\Generic.xaml):
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type l:IconView}, ResourceId=IconViewStyle}"
TargetType="{x:Type ListView}"
BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="ItemContainerStyle" Value="{Binding (ListView.View).ItemContainerStyle, RelativeSource={RelativeSource Self}}"/>
<Setter Property="ItemTemplate" Value="{Binding (ListView.View).ItemTemplate, RelativeSource={RelativeSource Self}}"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth, RelativeSource={RelativeSource AncestorType=ListView}}"
MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
ItemHeight="{Binding (ListView.View).ItemHeight, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
It's used in the corresponding Control class:
public class IconView : ViewBase
{
public static readonly DependencyProperty ItemContainerStyleProperty =
ItemsControl.ItemContainerStyleProperty.AddOwner(typeof(IconView));
public Style ItemContainerStyle
{
get { return (Style)GetValue(ItemContainerStyleProperty); }
set { SetValue(ItemContainerStyleProperty, value); }
}
public static readonly DependencyProperty ItemTemplateProperty =
ItemsControl.ItemTemplateProperty.AddOwner(typeof(IconView));
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public static readonly DependencyProperty ItemWidthProperty =
WrapPanel.ItemWidthProperty.AddOwner(typeof(IconView));
public double ItemWidth
{
get { return (double)GetValue(ItemWidthProperty); }
set { SetValue(ItemWidthProperty, value); }
}
public static readonly DependencyProperty ItemHeightProperty =
WrapPanel.ItemHeightProperty.AddOwner(typeof(IconView));
public double ItemHeight
{
get { return (double)GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
protected override object DefaultStyleKey
{
get
{
return new ComponentResourceKey(GetType(), "IconViewStyle");
}
}
}
And here's how all this is being used in the View.xaml (I'll omit the DataTrigger that assigns {DynamicResource IconView} to ListView's View, for brevity) :
<DataTemplate x:Key="IconViewItemTemplate">
<StackPanel Height="170" Width="170">
<Grid Width="150" Height="150" HorizontalAlignment="Center">
<Image Source="{Binding DefaultPicture.Path}" Margin="6,6,6,9"/>
</Grid>
<TextBlock Text="{Binding ID}" FontSize="13" HorizontalAlignment="Center" Margin="0,0,0,1" />
</StackPanel>
</DataTemplate>
<localControls:IconView x:Key="IconView"
ItemTemplate="{StaticResource IconViewItemTemplate}"
ItemWidth="180"/>
I am going nuts... And, to add to my frustration, Snoop doesn't see my application :-(
Please help! ;-)
Many thanks,
Alex
Most of your bindings might just be broken: (ListView.View).ItemWidth
The above path is interpreted differently than the paths you use in StoryBoard.TargetProperty for example. If you use parenthesis in a binding it signals a binding to an attached property.
From MSDN, emphasis mine:
The path is specified in XAML that is in a style or template that does not have a specified Target Type. A qualified usage is generally not valid for cases other than this, because in non-style, non-template cases, the property exists on an instance, not a type.
So change those respectively, in the above example: View.ItemWidth
Well, I found the culprit.
Turns out that problem is not in the snippets I included in the question, but rather in something I left out - the ListView.GroupStyle definition on the ListView itself.
After removing it, the list is shown the way I expect it to be.
Thank you to everyone that considered my question!
Alex

DataTrigger, Binding to nested properties via TemplatedParent

According to msdn, it should be perfectly legal, and possible, to bind something to a nested property:
<Binding Path="propertyName.propertyName2" .../>
<Binding Path="propertyName.propertyName2.propertyName3" .../>
In my case, it's not so, though...
I have a custom control, MyControl, with a dependency property ViewModel:
public static DependencyProperty ViewModelProperty = DependencyProperty.Register(
"ViewModel", typeof(IViewModel), typeof(MyControl));
public IViewModel ViewModel
{
get { return (IViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
and in the control template, I try to bind to properties in that viewmodel:
<Style TargetType="{x:Type my:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:MyControl}">
<Grid>
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text}"/>
<Button x:Name="MyButton" Content="Visible by trigger" Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.ButtonVisible}" Value="True">
<Setter TargetName="MyButton" Property="Visibility" Value="Visible" />
</DataTrigger>
.../>
In the viewmodel itself, I have a preoperty Text as follow:
public string Text
{
get { return m_text; }
set
{
m_text = value;
OnPropertyChanged("Text");
}
}
public bool ButtonVisible
{
get { return m_buttonVisible; }
set
{
m_buttonVisible = value;
OnPropertyChanged("ButtonVisible"); }
}
I get no bind errors, but things doesn't happend...
Any clues?
Edit
It looks like the bindings work half way. When the text is changed in the editbox, my Text property is set, but if the Text-property is set in code, the ui won't update.
Edit 2
Looks like my first attempt at simplifying the case before posting was a little to successful... As #Erno points out, the code that I posted seems to work OK.
I have looked at the original code some more, and added a trigger to the scenario. The original code uses triggers to show parts of the ui at given conditions. These are also binded to nested properties. I now think that these triggers fail to trigger. I have updated the code. If it still doesn't show whats wrong, I can post a sample application some where.
There is a comma missing:
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text}"/>
EDIT
Add Mode=TwoWay to the binding:
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ViewModel.Text, Mode=TwoWay}"/>
EDIT2
Got it! I could reproduce and fix it.
Replace the TemplatedParent with Self in the binding.
Read this explanation

How to display default text "--Select Team --" in combo box on pageload in WPF?

In a WPF app, in MVP app, I have a combo box,for which I display the data fetched from Database. Before the items added to the Combo box, I want to display the default text such as
" -- Select Team --"
so that on pageload it displays and on selecting it the text should be cleared and the items should be displayed.
Selecting data from DB is happening. I need to display the default text until the user selects an item from combo box.
Please guide me
The easiest way I've found to do this is:
<ComboBox Name="MyComboBox"
IsEditable="True"
IsReadOnly="True"
Text="-- Select Team --" />
You'll obviously need to add your other options, but this is probably the simplest way to do it.
There is however one downside to this method which is while the text inside your combo box will not be editable, it is still selectable. However, given the poor quality and complexity of every alternative I've found to date, this is probably the best option out there.
You can do this without any code behind by using a IValueConverter.
<Grid>
<ComboBox
x:Name="comboBox1"
ItemsSource="{Binding MyItemSource}" />
<TextBlock
Visibility="{Binding SelectedItem, ElementName=comboBox1, Converter={StaticResource NullToVisibilityConverter}}"
IsHitTestVisible="False"
Text="... Select Team ..." />
</Grid>
Here you have the converter class that you can re-use.
public class NullToVisibilityConverter : IValueConverter
{
#region Implementation of IValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
And finally, you need to declare your converter in a resource section.
<Converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
Where Converters is the place you have placed the converter class. An example is:
xmlns:Converters="clr-namespace:MyProject.Resources.Converters"
The very nice thing about this approach is no repetition of code in your code behind.
I like Tri Q's answer, but those value converters are a pain to use. PaulB did it with an event handler, but that's also unnecessary. Here's a pure XAML solution:
<ContentControl Content="{Binding YourChoices}">
<ContentControl.ContentTemplate>
<DataTemplate>
<Grid>
<ComboBox x:Name="cb" ItemsSource="{Binding}"/>
<TextBlock x:Name="tb" Text="Select Something" IsHitTestVisible="False" Visibility="Hidden"/>
</Grid>
<DataTemplate.Triggers>
<Trigger SourceName="cb" Property="SelectedItem" Value="{x:Null}">
<Setter TargetName="tb" Property="Visibility" Value="Visible"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
No one said a pure xaml solution has to be complicated. Here's a simple one, with 1 data trigger on the text box. Margin and position as desired
<Grid>
<ComboBox x:Name="mybox" ItemsSource="{Binding}"/>
<TextBlock Text="Select Something" IsHitTestVisible="False">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=mybox,Path=SelectedItem}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
Set IsEditable="True" on the ComboBox element. This will display the Text property of the ComboBox.
I dont know if it's directly supported but you could overlay the combo with a label and set it to hidden if the selection isn't null.
eg.
<Grid>
<ComboBox Text="Test" Height="23" SelectionChanged="comboBox1_SelectionChanged" Name="comboBox1" VerticalAlignment="Top" ItemsSource="{Binding Source=ABCD}" />
<TextBlock IsHitTestVisible="False" Margin="10,5,0,0" Name="txtSelectTeam" Foreground="Gray" Text="Select Team ..."></TextBlock>
</Grid>
Then in the selection changed handler ...
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
txtSelectTeam.Visibility = comboBox1.SelectedItem == null ? Visibility.Visible : Visibility.Hidden;
}
Based on IceForge's answer I prepared a reusable solution:
xaml style:
<Style x:Key="ComboBoxSelectOverlay" TargetType="TextBlock">
<Setter Property="Grid.ZIndex" Value="10"/>
<Setter Property="Foreground" Value="{x:Static SystemColors.GrayTextBrush}"/>
<Setter Property="Margin" Value="6,4,10,0"/>
<Setter Property="IsHitTestVisible" Value="False"/>
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
example of use:
<Grid>
<ComboBox x:Name="cmb"
ItemsSource="{Binding Teams}"
SelectedItem="{Binding SelectedTeam}"/>
<TextBlock DataContext="{Binding ElementName=cmb,Path=SelectedItem}"
Text=" -- Select Team --"
Style="{StaticResource ComboBoxSelectOverlay}"/>
</Grid>
Not tried it with combo boxes but this has worked for me with other controls...
ageektrapped blogpost
He uses the adorner layer here to display a watermark.
HappyNomad's solution was very good and helped me eventually arrive at this slightly different solution.
<ComboBox x:Name="ComboBoxUploadProject"
Grid.Row="2"
Width="200"
Height="23"
Margin="64,0,0,0"
ItemsSource="{Binding projectList}"
SelectedValue ="{Binding projectSelect}"
DisplayMemberPath="projectName"
SelectedValuePath="projectId"
>
<ComboBox.Template>
<ControlTemplate TargetType="ComboBox">
<Grid>
<ComboBox x:Name="cb"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}}"
SelectedValue ="{Binding SelectedValue, RelativeSource={RelativeSource TemplatedParent}}"
DisplayMemberPath="projectName"
SelectedValuePath="projectId"
/>
<TextBlock x:Name="tb" Text="Select Item..." Margin="3,3,0,0" IsHitTestVisible="False" Visibility="Hidden"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger SourceName="cb" Property="SelectedItem" Value="{x:Null}">
<Setter TargetName="tb" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ComboBox.Template>
</ComboBox>
Easiest way is to use CompositeCollection to merge default text and data from database directly in ComboBox e.g.
<ComboBox x:Name="SelectTeamComboBox" SelectedIndex="0">
<ComboBox.ItemsSource>
<CompositeCollection>
<ComboBoxItem Visibility="Collapsed">-- Select Team --</ComboBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource ResourceKey=MyComboOptions}}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
And in Resources define StaticResource to bind ComboBox options to your DataContext, because direct binding in CollectionContainer doesn't work correctly.
<Window.Resources>
<CollectionViewSource Source="{Binding}" x:Key="MyComboOptions" />
</Window.Resources>
This way you can define your ComboBox options only in xaml e.g.
<ComboBox x:Name="SelectTeamComboBox" SelectedIndex="0">
<ComboBox.ItemsSource>
<CompositeCollection>
<ComboBoxItem Visibility="Collapsed">-- Select Team --</ComboBoxItem>
<ComboBoxItem >Option 1</ComboBoxItem>
<ComboBoxItem >Option 2</ComboBoxItem>
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
I would recommend the following:
Define a behavior
public static class ComboBoxBehaviors
{
public static readonly DependencyProperty DefaultTextProperty =
DependencyProperty.RegisterAttached("DefaultText", typeof(String), typeof(ComboBox), new PropertyMetadata(null));
public static String GetDefaultText(DependencyObject obj)
{
return (String)obj.GetValue(DefaultTextProperty);
}
public static void SetDefaultText(DependencyObject obj, String value)
{
var combo = (ComboBox)obj;
RefreshDefaultText(combo, value);
combo.SelectionChanged += (sender, _) => RefreshDefaultText((ComboBox)sender, GetDefaultText((ComboBox)sender));
obj.SetValue(DefaultTextProperty, value);
}
static void RefreshDefaultText(ComboBox combo, string text)
{
// if item is selected and DefaultText is set
if (combo.SelectedIndex == -1 && !String.IsNullOrEmpty(text))
{
// Show DefaultText
var visual = new TextBlock()
{
FontStyle = FontStyles.Italic,
Text = text,
Foreground = Brushes.Gray
};
combo.Background = new VisualBrush(visual)
{
Stretch = Stretch.None,
AlignmentX = AlignmentX.Left,
AlignmentY = AlignmentY.Center,
Transform = new TranslateTransform(3, 0)
};
}
else
{
// Hide DefaultText
combo.Background = null;
}
}
}
User the behavior
<ComboBox Name="cmb" Margin="72,121,0,0" VerticalAlignment="Top"
local:ComboBoxBehaviors.DefaultText="-- Select Team --"/>
IceForge's answer was pretty close, and is AFAIK the easiest solution to this problem. But it missed something, as it wasn't working (at least for me, it never actually displays the text).
In the end, you can't just set the "Visibility" property of the TextBlock to "Hidden" in order for it to be hidden when the combo box's selected item isn't null; you have to SET it that way by default (since you can't check not null in triggers, by using a Setter in XAML at the same place as the Triggers.
Here's the actual solution based on his, the missing Setter being placed just before the Triggers:
<ComboBox x:Name="combo"/>
<TextBlock Text="--Select Team--" IsHitTestVisible="False">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Setters>
<Setter Property="Visibility" Value="Hidden"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=combo,Path=SelectedItem}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Not best practice..but works fine...
<ComboBox GotFocus="Focused" x:Name="combobox1" HorizontalAlignment="Left" Margin="8,29,0,0" VerticalAlignment="Top" Width="128" Height="117"/>
Code behind
public partial class MainWindow : Window
{
bool clearonce = true;
bool fillonce = true;
public MainWindow()
{
this.InitializeComponent();
combobox1.Items.Insert(0, " -- Select Team --");
combobox1.SelectedIndex = 0;
}
private void Focused(object sender, RoutedEventArgs e)
{
if(clearonce)
{
combobox1.Items.Clear();
clearonce = false;
}
if (fillonce)
{
//fill the combobox items here
for (int i = 0; i < 10; i++)
{
combobox1.Items.Insert(i, i);
}
fillonce = false;
}
}
}
I believe a watermark as mentioned in this post would work well in this case
There's a bit of code needed but you can reuse it for any combobox or textbox (and even passwordboxes) so I prefer this way
I am using an IsNullConverter class in my project and it worked for me.
here is the code for it in c#,create a folder named Converter and add this class in that folder,as the trigger used doesnt supports value for rather than null,and IsNullConverter just do that
public class IsNullConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value == null);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("IsNullConverter can only be used OneWay.");
}
}
add the namespace in xaml file like this.
xmlns:Converters="clr-namespace:TymeSheet.Converter"
means
xmlns:Converters="clr-namespace:YourProjectName.Converter"
use this line below the resources to make it availabe through xaml code
<Converters:IsNullConverter x:Key="isNullConverter" />
here is the xaml code,i used here the trigger so whenever an item is selected in the combobox the visibilty of your text becomes false.
<TextBlock Text="Select Project" IsHitTestVisible="False" FontFamily="/TimeSheet;component/Resources/#Open Sans" FontSize="14" Canvas.Right="191" Canvas.Top="22">
<TextBlock.Resources>
<Converters:IsNullConverter x:Key="isNullConverter"/>
</TextBlock.Resources>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ProjectComboBox,Path=SelectedItem,Converter={StaticResource isNullConverter}}" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
//XAML Code
// ViewModel code
private CategoryModel _SelectedCategory;
public CategoryModel SelectedCategory
{
get { return _SelectedCategory; }
set
{
_SelectedCategory = value;
OnPropertyChanged("SelectedCategory");
}
}
private ObservableCollection<CategoryModel> _Categories;
public ObservableCollection<CategoryModel> Categories
{
get { return _Categories; }
set
{
_Categories = value;
_Categories.Insert(0, new CategoryModel()
{
CategoryId = 0,
CategoryName = " -- Select Category -- "
});
SelectedCategory = _Categories[0];
OnPropertyChanged("Categories");
}
}
A little late but..
A more simple way would be to add a dummy data item to the list with parameter IsDummy=true and make sure it is not HitTestVisable and its hight is 1 pixel (using a Converter) so it wont be seen.
Than just register to SelectionChanged and in it, set the index to the dummy item index.
It works like a charm and this way you don't mess with the style and colors of the ComboBox or your application theme.
InitializeComponent()
yourcombobox.text=" -- Select Team --";
The above code demonstrates the simplest way to achieve it. After window load, declare the text of the combobox, using the .Text property of the combobox. This can be extended to the DatePicker, Textbox and other controls as well.
EDIT: Per comments below, this is not a solution. Not sure how I had it working, and can't check that project.
It's time to update this answer for the latest XAML.
Finding this SO question searching for a solution to this question, I then found that the updated XAML spec has a simple solution.
An attribute called "Placeholder" is now available to accomplish this task. It is as simple as this (in Visual Studio 2015):
<ComboBox x:Name="Selection" PlaceholderText="Select...">
<x:String>Item 1</x:String>
<x:String>Item 2</x:String>
<x:String>Item 3</x:String>
</ComboBox>
I did it before binding the combobox with data from database in codebehind like this -
Combobox.Items.Add("-- Select Team --");
Combobox.SelectedIndex = 0;
Put a label on top of the combobox.
Bind the content of the label to to the combobox Text property.
Set the opacity of the combobox to zero , Opacity=0.
Write default text in the combobox Text property
<ComboBox Name="cb"
Text="--Select Team--" Opacity="0"
Height="40" Width="140" >
<ComboBoxItem Content="Manchester United" />
<ComboBoxItem Content="Lester" />
</ComboBox>
</Grid>
This is old, but here's my idea in kind of MVVM style. I'm using Stylet MVVM framework.
This is View:
<UserControl x:Class="ComboBoxWithPlaceholderTextView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
>
<Grid>
<ComboBox
ItemsSource="{Binding ItemsSource}"
SelectedItem="{Binding SelectedItem}"
DropDownOpened="{s:Action DropDownOpened}"
DropDownClosed="{s:Action DropDownClosed}"
IsDropDownOpen="{Binding IsDropDownOpened}"
/>
</Grid>
and then in ViewModel
public class ComboBoxWithPlaceholderTextViewModel : Screen
{
private List<string> _itemsSource;
private string _placeholderText;
private string _selectedItem;
private bool _isDropDownOpened;
public bool IsDropDownOpened
{
get => _isDropDownOpened;
set
{
if (value == _isDropDownOpened)
{
return;
}
SetAndNotify(ref _isDropDownOpened, value);
}
}
public string SelectedItem
{
get
{
return _selectedItem;
}
set
{
SetAndNotify(ref _selectedItem, value);
}
}
public string PlaceholderText
{
get { return _placeholderText; }
set
{
if (value == _placeholderText)
{
return;
}
SetAndNotify(ref _placeholderText, value);
}
}
public List<string> ItemsSource
{
get { return _itemsSource; }
set
{
SetAndNotify(ref _itemsSource, value);
if (!IsDropDownOpened && (string.IsNullOrEmpty(SelectedItem) || !SelectedItem.Equals(PlaceholderText)))
{
ItemsSource.Insert(0, PlaceholderText);
SelectedItem = ItemsSource[0];
}
}
}
public void DropDownOpened()
{
ItemsSource.RemoveAt(0);
SelectedItem = null;
}
public void DropDownClosed()
{
if (SelectedItem is null)
{
ItemsSource.Insert(0, PlaceholderText);
SelectedItem = ItemsSource[0];
}
}
}
In this way I don't have to care if text will escape combo, but I have to care if placeholder text is selected.
Only set the IsEditable attribute to true
<ComboBox Name="comboBox1"
Text="--Select Team--"
IsEditable="true" <---- that's all!
IsReadOnly="true"/>
I know this is semi old but what about this way:
<DataTemplate x:Key="italComboWM">
<TextBlock FontSize="11" FontFamily="Segoe UI" FontStyle="Italic" Text="--Select an item--" />
</DataTemplate>
<ComboBox EmptySelectionBoxTemplate="{StaticResource italComboWM}" />

Resources