Since I'm still struggling with understanding how ItemContainerStyle works, I tried to go to the root component that defines its behavior, that is ItemsControl.
The simplest application of style I can think of is trying to apply a couple of settings, let say the Background and the Foreground to the item.
<Window.DataContext>
<local:VM></local:VM>
</Window.DataContext>
<DockPanel >
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Foreground" Value="red"/>
<Setter Property="Control.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Window>
The underlying class for the data is:
public class VM
{
public ObservableCollection<string> Items { get; set; } = new ObservableCollection<string>();
public VM()
{
Items.Add("first");
Items.Add("second");
Items.Add("third");
}
}
The result:
Ok, background is not applied, but this is not what I wanted to check and BTW in WPF there seem to be more exceptions than rules. (And BTW2 i've already fighted with assigning the background of a ListBox selected item, that requires to retemplate the whole thing, maybe here it's similar? If you know the answer it's appreciated, but I leave it for now because it's taking me off track).
Let's also have a look at the Visual Tree:
That is, for ItemsControl the items don't get a 'wrapper element'; if we do the same with a ListBox, for each item of the collection, it will be constructed a ListBoxItem.
Now let's try to template the item by adding this (just after </ItemsControl.ItemContainerStyle>) :
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<Label MaxWidth="100" Content="{Binding}"/>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
This is the result (they are moved in the center because of the MaxWidth="100"; I wanted to see if there was something behind):
The style is not applied anymore. Let's have a look at the Viusal Tree:
This visual tree is not surprising, we just replaced default representation that before was a TextBlock. In its place now we find a Label with its own standard sub-tree.
What's surprising is that at least Foreground should apply to the label too, but unfortunately it doesn't.
What's going on then?
I've read a very similar question here:
Can't use ItemTemplate and ItemContainerStyle together?
It differs from this in that it tries to assign the ContentTemplate. Since I'm still struggling with the basic behavior here (and I didn't understand the answer there except that there is some sort of copy-problem) I decided to put this more basic question.
However it seems there is a style-targeting problem here and not a copy problem; this is because if I keep the ItemTemplate, but replace the Label with a TextBlock (that leads to the very same VisualTree of the non-templated version) I get back my foreground red color!
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<TextBlock MaxWidth="100" Text="{Binding}"/>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
Getting warmer?
So it seems that the framework checks if the component is TextBlock and if not doesn't apply the style.
But this is the default behavior when applying implicit styles: a stile with (TargetType == the type of the control being styled).
In this case it seems like the framework assumes that the TargetType is TextBlock, and never reconsiders this assumption even if ItemTemplate is set.
In order to better understand how the style-target works here I tryed to set the style's TargetType explicitly, who knwos, so let's try this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="Label">
<Setter Property="Label.Foreground" Value="red"/>
<Setter Property="Label.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
See the TargetType="Label"? Great. It gives the error:
Cant apply to ContentPresenter a style intended for Label.
(translated from italian, maybe not the exact wording in english. plz replace with the exact one if you have it at hand).
That is, it expects this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Label.Foreground" Value="red"/>
<Setter Property="Label.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
It somewhat makes sense, because the root node of each item, according to the visual tree shown before is actually ContentPresenter.
At this point I'm quite confused: how is it supposed to work? The idea for the moment is that it doesn't.
The behavior for the subclasses like ListBox seems to be more sensible: it styles the container of the item; here a container for the item doesn't exist. That's just my guess because i couldn't find any documentation saying this.
You're looking at your items and thinking about them when setting the ItemContainerStyle.
But of course this is their container you're setting a style on. The container of each item. You don't really care about your container because it's not doing much.
Maybe a concrete example of a use case would be clearer than theory.
If you look at:
https://i.imgur.com/UZ6Nqrc.png
Those red and blue rectangles are units in this game.
Those are a variety of nato symbols indicating infantry, artillery cavalry etc.
An itemcontainerstyle is used to position them.
The whole panel on the left has an itemscontrol with a canvas as it's itemspanel ( instead of the default stackpanel ).
There is a viewmodel for each unit and a collection of these is bound to the itemssource of that itemscontrol.
A unit viewmodel has an X and Y property which is used to position the unit within that canvas.
The position of a unit is defined by a point which is the centre of it's view. Glossing over exactly why that is, I think this is interesting because the unit's viewmodel doesn't need to calculate the offset from centre to top left corner. This is done by a converter in the view and applied using a style:
<Style TargetType="ContentPresenter" x:Key="CenteredContentPresenter">
<Setter Property="Canvas.Top">
<Setter.Value>
<MultiBinding Converter="{local:MultiAddConverter}">
<Binding Path="Y" Mode="TwoWay"/>
<Binding Path="ActualHeight"
Converter="{local:MultiplyConverter Multiplier=-.5}"
RelativeSource="{RelativeSource Self}"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{local:MultiAddConverter}">
<Binding Path="X" Mode="TwoWay"/>
<Binding Path="ActualWidth"
Converter="{local:MultiplyConverter Multiplier=-.5}"
RelativeSource="{RelativeSource Self}"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
Elsewhere, in the map editor trees are positioned in a similar manner.
The ItemContainerStyle is applied to the item containers that gets created by the virtual GetContainerForItemOverride method.
In the ItemsControl base class, this method returns a ContentPresenter:
protected virtual DependencyObject GetContainerForItemOverride()
{
return new ContentPresenter();
}
In the derived ListBox class, it returns a ListBoxItem:
protected override DependencyObject GetContainerForItemOverride()
{
return new ListBoxItem();
}
The TargetType of the ItemContainerStyle must match the type of the DependencyObject returned from this method. Otherwise, you'll get an exception when the style is applied to the container(s) at runtime.
I have WPF application with a couple of buttons on which there's no text, only a vector-based image (using a Path object), the ControlTemplate looks like this:
<ControlTemplate x:Key="IconButtonContentTemplate" TargetType="{x:Type ButtonBase}">
<Grid Background="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}">
<Path HorizontalAlignment="Center" VerticalAlignment="Center"
Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}"
Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}"
Data="{Binding (components:ImageButtonAttachedProperties.ImagePathData), RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}"
Stretch="Uniform" Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="LightGray" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Foreground" Value="LightGray" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style TargetType="{x:Type Button}" x:Key="ClockButtonStyle" BasedOn="{StaticResource IconButtonStyle}">
<Setter Property="Template" Value="{StaticResource IconButtonContentTemplate}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="components:ImageButtonAttachedProperties.ImagePathData" Value="M69.349,65.092C68.714,65.092,68.072,64.925,67.487,64.577L46.294,51.946 46.294,21.239C46.294,19.227 47.925,17.595 49.938,17.595 51.949,17.595 53.581,19.227 53.581,21.239L53.581,47.807 71.217,58.317C72.945,59.348 73.512,61.585 72.483,63.312 71.799,64.457 70.589,65.092 69.349,65.092z M49.938,3.877C24.573,3.877 3.938,24.513 3.938,49.877 3.938,75.241 24.573,95.877 49.938,95.877 75.302,95.877 95.938,75.241 95.938,49.877 95.938,24.513 75.302,3.877 49.938,3.877z M52.876,88.467C52.882,88.395 52.897,88.324 52.897,88.25 52.897,86.615 51.572,85.29 49.937,85.29 48.302,85.29 46.977,86.615 46.977,88.25 46.977,88.324 46.994,88.395 46.999,88.467 27.994,87.032 12.783,71.822 11.349,52.817 11.423,52.822 11.492,52.838 11.567,52.838 13.202,52.838 14.527,51.513 14.527,49.878 14.527,48.243 13.201,46.918 11.567,46.918 11.492,46.918 11.422,46.935 11.349,46.94 12.783,27.933 27.994,12.722 47,11.287 46.995,11.36 46.978,11.43 46.978,11.504 46.978,13.139 48.304,14.464 49.938,14.464 51.572,14.464 52.897,13.138 52.897,11.504 52.897,11.429 52.881,11.36 52.876,11.287 71.882,12.722 87.093,27.932 88.528,46.938 88.455,46.933 88.385,46.916 88.311,46.916 86.676,46.916 85.35,48.242 85.35,49.876 85.35,51.51 86.676,52.836 88.311,52.836 88.385,52.836 88.455,52.82 88.528,52.815 87.094,71.822 71.883,87.032 52.876,88.467z" />
</Style>
My issue is that on very rare occasions the button image is not shown (99% of the time it does). The button can still be clicked, but the image on it is not shown.
I'm not sure what's causing this. The data binding on the vector image? Or the data binding on the fill color?
ImageButtonAttachedProperties.ImagePathData is System.Windows.Media.Geometry object, which describes the vector-image.
public static class ImageButtonAttachedProperties
{
public static readonly DependencyProperty ImagePathDataProperty =
DependencyProperty.RegisterAttached("ImagePathData", typeof(Geometry), typeof(ImageButtonAttachedProperties), new UIPropertyMetadata(null));
public static Geometry GetImagePathData(DependencyObject obj)
{
return (Geometry)obj.GetValue(ImagePathDataProperty);
}
public static void SetImagePathData(DependencyObject obj, Geometry value)
{
obj.SetValue(ImagePathDataProperty, value);
}
}
Any idea what I'm doing wrong here?
You're binding the desired dimensions of a template element to the final dimensions of the templated parent; this creates a circular dependency. Don't do that.
The size of the templated control (e.g., your Button) depends on the desired size of its template content (e.g., your Path). If you make the size of the content dependent on the size of the templated parent, you're just asking for trouble.
If I had to guess, you may be getting caught in a layout loop, where one layout update triggers another to occur on the next tick. These aren't always obvious (except, perhaps, by looking at CPU usage), because of the way layout updates are scheduled. I've seen this happen before, e.g., when the 'Arrange' pass invalidates the results of the 'Measure' pass. The visible effects can be rather unpredictable: sometimes things appear to be working fine, until they don't.
Get rid of the Width and Height bindings on your Path, and set the horizontal and vertical alignments to Stretch.
You should:
1) Remove the databinding on the fill color to determine if you can still re-produce the problem.
I you cannot, try it the other way around:
2) Remove the databinding on the Path (use a constant Path for testing) and test with the databinding on the fill color.
If 1) and 2) could not reproduce your problem then try:
3) Use constant values on the fill color and the databinding on the Path
If you were able to re-produce the problem in 1) or 2) you now know at least the source of the problem. The same is true for 3) but it is more complicated because 3) implies that both databindings somehow influence each other ...
4) Its also possible that your problem is burried deeper in your themes defintion - so switching custom themes off and using only Generics themes is also a way to test in order to learn the source of the issue.
5) I would also try to layout the Path on the Background of the Button to see if this behaves better with regard to your problem - see my last comment in this post:
Image fill the space on the button in WPF
You really need to post a small demo app to get a deterministic answer, otherwise, I am afraid that it is impossible to tell the exact source given only the snippets shown
I'm not sure what's causing this.
At some point the values of either ActualWidth or ActualHeight (on any control) are actually zero and those properties are read only type dependency properties. That happens when the control is Loaded, Measured, Arranged, or Rendered.
As per the FrameworkElement.ActualWidth Property
Because ActualWidth is a calculated value, you should be aware that
there could be multiple or incremental reported changes to it as a
result of various operations by the layout system. The layout system
may be calculating required measure space for child elements,
constraints by the parent element, and so on.
The question is that something is jiggering your button and causing a resize and you win the lottery by catching a zero value.
As a course of something to try, I provided an answer about housing a vector image Best way to use a vector image in WPF in two different ways, and the example I used had 3 vectors in a resizable window and to my knowledge they didn't flash except to redraw. But I had the height/width set to stretch.
Maybe change how the vector is held?
I have a control which has a default value for a property. When the control first gets its dataContext set, it assigns this property automatically.
In the xaml now, I want it to be possible to UNset this property. I've tried setting it to x:Null of just the empty string, but then I get an error because there's no converter for the property. How do I simply unassign this property from the xaml in the rare cases where I want the feature disabled?
code where it is originally set:
void OmniBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if( e.NewValue is BindingObjectBaseExtended )
{
BindingObjectBaseExtended value = (BindingObjectBaseExtended)e.NewValue;
this.SetBinding(OmniBox.ContextValidationMessagesProperty, new Binding() { Source = value, Path = new PropertyPath("ValidationMessages") });
}
}
xaml where I want to unset the property.
<Style TargetType="ui:OmniBox">
<Setter Property="ContextValidationMessages" Value="" />
</Style>
Note that if I do not set up the binding automatically when the data context changes, then by default there are no validation messages and I have to do the following in the xaml to set them up:
<Style TargetType="ui:OmniBox">
<Setter Property="ContextValidationMessages" Value="ValidationMessages" />
</Style>
What I'm trying to do is make the above binding the default for my custom OmniBox control, and allow the user to unset it or change it to something else.
DependencyProperty.UnsetValue cannot be used in XAML.
http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty.unsetvalue(v=vs.90).ASPX
Personally, I would create a separate dependency property, such as bool AutoBindValidation and make it default to true. If it is false, don't do anything when the DataContext changes. This is a little more self-documenting. Depending on what exactly you're trying to do, you might not want to publicly expose ContextValidationMessages at all.
If you really want to do it the way you posted, I'm not sure why setting it to {x:Null} would cause an error (unless the property type is not nullable). But this approach would have problems because DataContextChanged is going to occur after the XAML is parsed. So the user can set it to {x:Null}, but then the DataContext will change and your code will set up the default binding and trample the user's value. You could set up the binding in the control's contstructor, but then if the DataContext does not have a ValidationMessages property, your control will be spitting out binding errors.
This may be impossible, my best bet was this:
<Setter Property="ContextValidationMessages"
Value="{x:Static DependencyProperty.UnsetValue}" />
But that throws "Cannot unset setter value". So you better inverse your logic or keep the property unset another way.
I don't think there is any supported way to do this in the xaml itself. In your code you are setting a local value on the ContextValidationMessagesProperty. The Style setters you included would have a lower dependency property precedence and even if they were evaluated they would set a value based on the specified Value - not clear it. Maybe instead of setting the binding in code you could have a Setter in your default style for OmniBox that sets that property - e.g.
<Setter Property="ContextValidationMessages" Value="{Binding ValidationMessages}" />
If you have to conditionally set the Binding then you could create a custom IValueConverter that checks for the specified type (passed as the parameter). e.g.
public class IsAssignableFromConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Type typeParameter = parameter as Type;
if (typeParameter == null)
return DependencyProperty.UnsetValue;
return value != null && typeParameter.IsAssignableFrom(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
Then you might use it like this:
<local:IsAssignableFromConverter x:Key="isAssignableConverter" />
<Style TargetType="ui:OmniBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource isAssignableConverter}, ConverterParameter={x:Type ui:BindingObjectBaseExtended}}" Value="True">
<Setter Property="ContextValidationMessages" Value="{Binding ValidationMessages}" />
</DataTrigger>
</Style.Triggers>
</Style>
In the case where you don't want this property to be applied you might set the Style for that instance of the OmniBox to a new style and make sure to set the OverridesDefaultStyle property to true.
I suppose another option is to create another dependency property that will call ClearValue on the ContextValidationMessages property but this seems like it could be a maintenance issue.
For certain cases you can 'reset' to the default value of the parent control by using a RelativeSource. For instance I'm using a DataGrid and this worked for me to reset back to the 'default'.
This is a textblock inside a datagrid cell.
<TextBlock Text="{Binding ServiceName}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<!-- Change text color to purple for FedEx -->
<Trigger Property="TextBlock.Text" Value="FedEx">
<Setter Property="TextBlock.Foreground" Value="Purple"/>
</Trigger>
<!-- Reset if the cell is selected, since purple on blue is illegible -->
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}}" Value="True">
<Setter Property="TextBlock.Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
This seems clever enough to inherit the correct color even when the window is inactive.
I'm having an issue when trying to do something which should be as easy as. I've attempted to use a Trigger based on a DependencyProperty or a DataTrigger - I can't get either to work.
XAML for the trigger is:
<Style x:Key="FileWatchButton" BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="Main:Main.XmlFilesAvailableForLoading" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
And the associated code-behind is:
public static readonly DependencyProperty XmlFilesAvailableForLoadingProperty =
DependencyProperty.Register("XmlFilesAvailableForLoading", typeof(bool), typeof(Main));
public bool XmlFilesAvailableForLoading
{
get
{
try
{
return (bool)this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.DataBind,
(System.Windows.Threading.DispatcherOperationCallback)delegate { return GetValue(XmlFilesAvailableForLoadingProperty); },
XmlFilesAvailableForLoadingProperty);
}
catch (Exception)
{
return (bool)XmlFilesAvailableForLoadingProperty.DefaultMetadata.DefaultValue;
}
}
set
{
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.DataBind,
(System.Threading.SendOrPostCallback)delegate{ SetValue(XmlFilesAvailableForLoadingProperty, value); }, value);
}
}
Basically the dp is being set correctly by the presenter (it's based on a FileSystemWatcher class looking for one or more files) but the Trigger is not being fired. Is this a threading issue?
Thanks.
It's not clear if the code is complete, but it looks like the Property path in your trigger may be wrong. Does the button being styled have a Main property? I am guessing not; it looks like you are trying to trigger on a property of a different element, called Main -- is that right?
In any case, the namespace prefix is not required. If the button has a property named Main, then you can address this directly; if it doesn't, then the prefix won't help you.
My guess is that you probably need a DataTrigger whose binding refers to the Main element:
<local:Main Name="MyMain" ... /> <!-- this has the XmlFilesAvailableForLoading property -->
<DataTrigger Binding="{Binding XmlFilesAvailableForLoading, ElementName=MyMain}"
Value=True>
<Setter Property="Background" Value="Red" />
</DataTrigger>
On an unrelated note, you should have any non-boilerplate implementation in your DP getter and setter. Remember that the binding and styling system will bypass the getter and setter and talk directly to the underlying storage. So I'd strongly advise changing these back to just plain GetValue and SetValue calls.
I have a ListBox bound to a list of Items (for arguement, lets say its got a string and two dates Entered and Done).
I would like to make the background color of items in the ListBox have a gray color if the Done DateTime is != DateTime.MinValue.
Edit:
Should I make a converter? and convert DateTime to a Brush based on the value of the DateTime?
Is something like this my best option? or is there a simple Xaml snippet I could use?
[ValueConversion(typeof(DateTime), typeof(Brush))]
class MyConverter : IValueConverter
{
...
}
A ValueConverter would work. Another option would be to use a DataTrigger in the style of ListBoxItem. Maybe something like this:
<Style x:Name="MinDateTimeListBoxStyle" TargetType="ListBoxItem">
<Style.Triggers>
<Setter Property="Background" Value="Gray" />
<DataTrigger Binding="{Binding Path=Done}"
Value="{x:Static sys:DateTime.MinValue}">
<Setter Property="Background" Value="White" />
</DataTrigger>
</Style.Triggers>
</Style>
This will set the background to Gray when the value of Done isn't DateTime.MinValue. I don't think there is a way to do a not equals comparison in a trigger, so it sets the background to Gray by default, and only changing it back to white if Done hasn't changed yet. It would probably be better to use the correct color for the background instead of white (maybe get the value of the parent's background?), but this should give you something to start with.
Update: To apply this style to the items of only certain ListBoxes, give the style a name and set the ItemContainerStyle as appropriate:
<ListBox x:Name="StyledListBox"
ItemContainerStyle="{StaticResource MinDateTimeListBoxStyle}" />
<ListBox x:Name="NormalListBox" />