Setting TaskbarItemInfo via WPF Style or Trigger - wpf

WPF 4 includes a "TaskbarItemInfo" Freezable class that adds an attached property to a Window that allows various Windows 7 taskbar items to be changed.
In particular, I'm trying to set progress information on the tasbar icon for the Window. I'd like to use a DataTrigger to do this, but it doesn't seem to work. I tried using a simple style setter, but that doesn't work either - only direct property assignment or direct property bindings will work.
For example:
<Window.Style>
<Style>
<Setter Property="TaskbarItemInfo.ProgressState" Value="Indeterminate" />
</Style>
</Window.Style>
<Window.TaskbarItemInfo>
<TaskbarItemInfo />
</Window.TaskbarItemInfo>
It appears as though the attached property is not being set via the style. Is my syntax for setting attached properties via styles incorrect, or am I missing something else?

TaskbarItemInfo doesn't inherit from FrameworkElement so there is no Style property for you to set in the DataTrigger.
Why don't you bind your TaskbarItemInfo's ProgressState to the property you were thinking of using in your DataTrigger and then use a ValueConverter to convert that to the relevant TaskbarItemProgressState.
<Window.TaskbarItemInfo>
<TaskbarItemInfo ProgressState="{Binding YourProperty, Mode=OneWay, Converter={StaticResource ProgressStateConverter}}" />
</Window.TaskbarItemInfo>
Then a simple converter can return whichever TaskbarItemProgressState applies to your trigger property.

Related

ContentPresenter Resources not applied when added as LogicalChild

My custom control is derived from ContentControl and has an additional dependency property 'AdditionalContent' of type FrameworkElement.
This property is bound to a ContentPresenter in style that has custom style resources:
<ContentPresenter ContentSource="AdditionalContent">
<ContentPresenter.Resources>
<Style TargetType="{x:Type Button}">
... some setters ...
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
As I learned from other questions here, I have to add this object as logical child of my control by calling AddLogicalChild(AdditionalContent) and overriding LogicalChildren property.
Now, if I use my control like this
<MyControl>
<MyControl.AdditionalContent>
<Button .../>
</MyControl.AdditionalContent>
</MyControl>
The style for Button is not applied. And that's the correct behaviour, because of style inheritance (see this answer). So I have to apply the style in the place where I define the AdditionalContent. So far so good.
But strange behaviour: when I leave out adding the object as logical child, the styles are applied.
Why does this happen? And is there a proper way to provide styles for all contents inside AdditionalContent similar to define Toolbar styles?
It's hard to tell since you have left out much of the button definition, but try setting the style of the button to a dynamic resource with the button type as the resource key.
<Button Style="{DynamicResource {x:Type Button}}"/>
When adding a default style with no resource key like you have done, the implicit key is the data type.
By setting the style to a dynamic resource you are indicating that the resource could change during runtime, which is the case when you are inserting it into the tree at runtime like you are doing.

Best way to bind background color property of a custom control

I am creating a custom button control in WPF, and when I try to use it, the background brush property in the designer, it changes nothing. This is of course because I need to bind the properties together in XAML. I've tried:
<Button Content="Button" Background="{Binding Background, FallbackValue=White}"/>
If I don't use the fallbackvalue, I am able to use the designer to set the background color, but using it, I can't which means I cannot set a default. Any ideas?
If it is a Custom Control (not UserControl), and there is a Control Style with a ControlTemplate, try this:
Background="{TemplateBinding CtlBackground}"
Assuming that "CtlBackground" is the name of your dependency property for background (of course, you can use the standard dependency property "Background").

WPF - Set child controls of a user control to be readonly using binding

I have a WPF application with many different controls. I need to be able to set all child controls to be read only based on a property in my view model that I want to bind to.
There are a couple of challenges that I see:
How to ensure that setting the parent control to read only, also sets the child controls
Not all controls have a ReadOnly property - some IsReadOnly, some only have an IsEnabled
Has anyone any views on a generic solution rather than me having to bind the appropriate property (ReadOnly, IsReadOnly, etc) for each individual control?
Is there some way that I could use an attached property? Is there anyway, for example, that I could set a property on a grid, then in the code iterate through each child control setting it's appropriate property (if applicable at all)?
Any ideas welcomed.
David
I would recommend to do this using WPF implicit styles. The style would contain the Binding to the view model, for example:
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="{Binding IsNotProcessing}" />
</Style>
As this style does not have the x:Key attribute set and uses the x:Type markup extension on the TargetType attribute, it is implicitly applied to all buttons in this case.
You would have to write an implicit style for each distinct control in your view as the following style would not be applied to all your buttons, text boxes and whatever controls you use (although the IsEnabled property is defined on FrameworkElement):
<!-- This implicit style is not applied as the x:Type must be the same type as
the targeted control; inheritance does not work here. -->
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="IsEnabled" Value="{Binding IsNotProcessing}" />
</Style>
Another option would be to make a single style that has a resource key an then reference this from every control, which is also quite cumbersome, but could be done relatively easy using Blend if you know all the controls at design time (you would select all controls and then apply the style using the properties window).
Hope this will help you.
Use the property IsHitTestVisible in xaml file to make real read only
<Grid IsHitTestVisible = "False">
//put a control
</Grid>

Collapse ContentControl if Content is Collapsed

I've got a ContentControl which has a style containing a border and other visual decorations. I want these decorations to disappear when the content is collapsed, so I figured I have to set the visibility of the ContentControl to collapsed in this case. I got this style for my ContentControl decoration:
<Style x:Key="DecoratedItem1" TargetType="{x:Type c:DecoratedItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:DecoratedItem}">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="2" CornerRadius="2">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/file.png"/>
<ContentPresenter Name="wContent"/>
</StackPanel>
</Border>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
<DataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger.Setters>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The DecoratedItem class is just a subclass of ContentControl with additional DependencyProperties which are not relevant to this issue, I just wanted to note that I already have a subclass to which I could add code, if necessary.
This works when the content of the ContentControl is a UIElement, however if the content is generated by a DataTemplate it complains about not being able to find the Visibility property.
<!-- works -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
<!-- doesn't work -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
<c:DecoratedItem.Resources>
<DataTemplate DataType="{x:Type clr:String}">
<TextBlock Text="{Binding}" Visibility="Collapsed"/>
</DataTemplate>
</c:DecoratedItem.Resources>
</c:DecoratedItem>
The error for the second case diplayed in the debug output window is:
System.Windows.Data Error: 40 : BindingExpression path error:
'Visibility' property not found on 'object' ''String' (HashCode=-885832486)'.
BindingExpression:Path=Content.Visibility;
DataItem='ContentPresenter' (Name='wContent');
target element is 'DecoratedItem' (Name='');
target property is 'NoTarget' (type 'Object')
I understand why this happens, but don't know how to fix my style to work as I want it. I don't mind adding helper code to the DecoratedItem subclass if necessary. Any idea how to fix this?
[edit 1]
Some more explanation in regard to the proposed answer:
I can't enforce that the Content is always an UIElement. This is a model-view design after all, and of course I simplified the example a lot. In the real project the content is a model selected from the DataContext, which can be of several different types, and the DataTemplate builds a presentation for that model. Some of the DataTemplates decide (depending on model-state) that there is nothing to present and switch Visibility to Collapsed. I would like to propagate that information to the decorating container. The example above really just presents the problem and not the motivation, sorry.
[edit 2]
Not sure how knowing more about the model would help the problem, but here we go. The data in the Content field doesn't have much in common since it can be a lot of things, this DecoratedItem is supposed to be reusable to give a common visual style to items shown on some forms. Content can be stuff like work items whose DataTemplate collapses them if they are disabled; other kinds of Content can be incomplete and get collapsed. Of course other kinds never may get collapsed.
But note that the data model doesn't really have much to do with the question, which still is how to bind against the Visibility of the expanded content element (after possibly exposing it through the subclass in a bindable way).
There are a couple of ways of describing what's wrong. In the first, working example:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
the Content property of the ContentControl is set to be a TextBlock, which is a UIElement with a Visibility property. (This assumes that you have not changed the ContentPropertyAttribute of your derived class DecoratedItem to be something other than Content). Thus, your DataTrigger binding can correctly evaluate:
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
Contrast the working case with the failing one:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
in which the Content property is set to an instance of a String, which does not have a Visibility property.
The other way to describe what's wrong is to note that, even though you supply a DataTemplate for the case of Content being a String, Content is still a String and still does not have a Visibility property. In other words, the statement that the Content is generated by the DataTemplate is incorrect -- your DataTemplate just told the control how to display Content of type String.
The general answer to your question is that, if you want the DataTrigger in DecoratedItem1 bound with a Path of Content.Visibility, you need to make sure the content you put in it is always a UIElement. Conversely, if you want to be able to put any sort of Content into the control, you need to trigger off of something else.
The specific answer to your question, strictly, relies on your broader intent (in particular, on how the Visibility of the Content of your control will be set/modified). A couple of possibilities:
if you really want your DataTrigger binding of the form, "Content.Visibility", make sure that the Content is always a UIElement. For instance, use the working form of the style and then bind the Text of TextBlock to something appropriate. However, this doesn't fit so well with the idea of your derived control as a ContentControl, so...
your DataTrigger could probably bind to something else. It seems like, from the way the question is formed, that there is some other property or code-behind that will control whether the various content entities are Visible or not.
finally, you could add an additional DataTrigger to the TextBlock. This DataTrigger would set the visibility of its parent based on its own visibility. Then, bind the DataTrigger in style DecoratedItem1 with Path "Visibility" instead of "Content.Visibility", essentially chaining together Visibilities manually.
Edit
Based on what you've described about how you want to use this, it sounds like you need to consider the visual tree. You might augment your DecoratedItem control to have the following functionality: if all its visual children that are UIElments have a visibility of Collapsed (or if it has no visual children), it is also Collapsed (or, whatever logic makes sense for the desired functionality in terms of the Visibility of its visual children). You'd need to use the VisualTreeHelper class from code -- in particular, the GetChildrenCount and GetChild methods. You'd also, in your DecoratedItem class, override OnVisualChildrenChanged (while still calling the base class method) so that you can get UIElement.IsVisibleChanged events for the visible children.

What's the difference between a Trigger and a DataTrigger?

They seem the same. Is there a significant difference? I think I am missing something.
A regular Trigger only responds to dependency properties.
A DataTrigger can be triggered by any .NET property (by setting its Binding property). However, its setters can still target only dependency properties.
Another difference is that a DataTrigger can be bound to another control, a StaticResource, etc etc.
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger
Binding="{Binding SomeProperty,
ElementName=someOtherControl"
Value="Derp">
<!-- etc -->
You can only examine the instance on which the style is set when using a Trigger. For example, a Trigger applied to a Button can inspect the value of IsPressed, but it would not be able to inspect the (for example) Text value of a TextBox on the same form if you wished to disable the Button if the TextBox was empty.
The short answer (as I'm about to sleep)- A trigger works on dependency properties (typically GUI properties) whereas data triggers can be triggered by any .NET property (typically a property in a ViewModel that implements INotifyPropertyChanged).

Resources