ContentPresenter Resources not applied when added as LogicalChild - wpf

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.

Related

Style Triggers Cause Control To Lose Theme

I have a WPF application using MahApps Metro for it's UI theming. I also need to use style triggers so I can appropriately determine whether a control is visible based on a property. The triggers work, but have the side effect of removing the theme. So now it looks like a default WPF unthemed CheckBox. Is there any way to preserve the MahApps Metro theme on the CheckBox?
When you assign a style, you overwrite the default style or any style applied before. If you want to extend a style, you have specify the base style using the BasedOn property.
Styles can be based on other styles through this property. When you use this property, the new style will inherit the values of the original style that are not explicitly redefined in the new style.
Specify the control type to base your style on the implicit style of the control.
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource {x:Type CheckBox}}">
<!-- ...your setters and triggers. -->
</Style>
Specify the key of the style that you want to base your style on.
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource MyCheckBoxBaseStyle}">
<!-- ...your setters and triggers. -->
</Style>
The named styles for CheckBox in MahApps can be found here on GitHub.
Please be aware that although your Visibility triggers should work, other triggers that are already defined in the control template of the CheckBox styles take precedence and you will not be able to redefine them in your own style. If you ever hit that case, you have to copy the corresponding style from GitHub into your project and adapt it to your requirements.

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.

inherit a style or find the style programmatically

I have a third party control that I assume gets a style from somewhere.
I have an subclass of that control, where I add an event handler. but now when I replace the old control in xaml with my overrided control, the style gets lost. I assume that its distinguishing between the superclass and subclass when it applies the style. How do I tell it that subclasses, like MyButton:ThirdPartyButton, should have the same style as ThirdPartyButton.
Or is there a programmatic way to see the source of the style like
ThirdPartyButton.GetDefaultStyleLocation();
Define a style in the resources which is implicitly applied:
<Style TargetType="{x:Type local:MySubclass}"
BasedOn="{StaticResource {x:Type thirdParty:Control}}"/>
This is necessary since styles are sadly not inherited.

How to apply styles to the whole silverlight application?

I have created two different grid background and radio button style in my
App.xaml.
User can select any style to change the look of the page i.e: Changing the background and style of the radiobutton.
Now When I click on the raduio button the application navigates to another page and the style disappears.
Is there a way to Set the style in application level or I need to store the styleVar as Global Var and check on the second page load and then apply the styles as per the styleVar.
Yes like Jeff Wilcox said Implicit styling is a new thing in Silverlight 4. So if you want to create a style that is the default for all the controls of that type in the range XAML file or the whole application if placed in App.xaml you would leave out the x:Key attribute.
<Style x:Key="ButtonStyle" TargetType="Button">
To use ButtonStyle you would have to write:
<Button Content="A button" Style="{StaticResource ButtonStyle}" />
Leaving out the x:Key would allow you to use ButtonStyle as default.
<Style TargetType="Button">
<Button Content="A button with style that has no x:Key value" />
Now if you'd need to create a button that doesn't have this default style, you can set the Style property of that button to be x:Null or override by setting a named style to that button.
<Button Content="Default Silverlight button" Style="{x:Null}"/>
Another new thing with Styles in Silverlight 4 is that you can create new styles that are based on existing ones. Although it wasn't your question I'll give an example:
<Style TargetType="Button" BasedOn="{StaticResource BasedStyle}">
About implicit styling in the docs: http://msdn.microsoft.com/en-us/library/system.windows.style%28VS.95%29.aspx
Implicit Styles
In Silverlight 4, you can set styles
implicitly. That is, you can apply a
certain style to all elements of a
certain type. When a resource
is declared without an x:Key value,
the x:Key value assumes the value of
the TargetType property. If you set
the style implicitly, the style is
applied only to the types that match
the TargetType exactly and not to
elements derived from the TargetType
value. For example, if you create a
style implicitly for all the
ToggleButton controls in your
application, and your application has
ToggleButton and CheckBox controls
(CheckBox derives from ToggleButton),
the style is applied only to the
ToggleButton controls.
BasedOn Styles
Starting with Silverlight 3, it is
possible to build a new style based on
an existing style. You can do this
using the BasedOn property. This
reduces the duplication of code and
makes it easier to manage resources.
Each style supports only one BasedOn
style. For more information, see the
BasedOn property.
Just leave off the x:Key part of the Style, inside App.xaml. This is a new feature for Silverlight 4.
Place the styles in question in the App.xaml file. The application objects Resources property makes Styles and other resources available across the entire application.

Resources