I have a parent contentcontrol which displays data via a datatemplate. The datatemplate contains a stackpanel with several usercontols of the same type. I like to set the property only once on the parent control, it must set the value of the property on all the subcontrols. But if there is a way to do it on the stackpanel it's also OK. The template can be changed at runtime and the values need also to be propagated to the new template.
My current solution is to implement the property on both parent and subcontrol and use code to propagate the value from the parent to all the subcontrols. My question is: is there a better or other ways of doing this?
EDIT:
Some notes of clarification to my question. The application is currently WPF, but if it's portable to silverlight it would be a bonus. The property is a dependency of the type Style.
I want to use it to style part of the subcontrol. Currently the datatemplate is stored in a separate resource dictionary, so it can be reused. The visuals of the subcontrol are styled via controltemplate. The template contains three different controls, the first one is a label. The need (desire, foolish wish) is to set the style only once, to give the label on all the subcontrols in the datatemplate a consistent look and feel.
So the crux of the problem is to override the value of the a style dependency property on a subcontrol, stored in a resource dictionary from a container control. Both are custom user controls, so all options are open.
<Parent SubSubStyle="x" Template="template" />
<DataTemplate x:Key=template>
<StackPanel>
<Subcontrol SubSubStyle="?"/>
<Subcontrol SubSubStyle="?"/>
<Subcontrol SubSubStyle="?"/>
<Subcontrol SubSubStyle="?"/>
</StackPanel>
</DataTemplate>
Is the property that you're trying to set a DependencyProperty that you have created? If so, the ideal thing to do in WPF is to define the property such that it will be inherited by elements in the visual tree.
If it's not your own dependency property (or if you're using Silverlight which does not support this mechanism) then you should instead use implicit styles.
public class MyControl {
// be prepared for some dependency property hell below
// this defines a DependencyProperty whose value will be inherited
// by child elements in the visual tree that do not override
// the value. An example of such a property is the FontFamily
// property. You can set it on a parent element and it will be
// inherited by child elements that do not override it.
public static readonly DependencyProperty MyInheritedProperty =
DependencyProperty.Register(
"MyInherited",
typeof(string),
typeof(MyControl),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.Inherits
)
);
}
Style is best option http://msdn.microsoft.com/en-us/library/ms745683.aspx#styling_basics
Related
I want to build a custom control in WPF but I don't know what to use. I want to achieve the following:
Control sketch
So there are two different textblocks (header and hint) and also a path for the icon.
I need this control multiple times in one view but the itemssource is not a list (so I can't take an itemscontrol and a datatemplate).
How would I do it the best way?
In Visual Studio there is a template for WPF Custom Controls Library (as well as a template for WPF custom control)
I suppose you're used to creating XAML so the markup will not be covered
To allow binding data items to your control from XAML you need to create dependency properties in your control code:
public string Header {
get => (string) GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);}
public static readonly DependencyProperty HeaderProperty = // note naming - <PropName>+Property
DependencyProperty.Register(nameof(Header), typeof(string), typeof(YourControl), new FrameworkPropertyMetadata(""));
Repeat this code for each property you want to expose
This way you can bind properties in XAML using <YourControl Header="text" /> or <YourControl Header={Binding SomeProp} />
Once you created all the needed props, you can easily bind to them in control's XAML:
<TextBlock Text={Binding Header} />
I have a class library, in which I've created default styles for TextBlock, which is applied to every TextBlock in any application that uses this class library.
The problem is that I sometimes need to exclude TextBlocks inside some other controls (say, ribbon, or my own Custom Control). The textblocks are not accessible, for instance the ones inside a tab item heade.
Is there any ways to force wpf to use another style for all TextBlocks inside one control?
Thanks
Simply add an empty (or any other) style to the resource dictionary of any ancestor of the TextBlock nested inside the control referencing the resource dictionary, in which the global style is defined. For instance, the global style defined for TextBlock won't be applied in this case (if the resource dictionary is referenced by an ancestor of the Button control):
<Button>
<Button.Resources>
<Style TargetType="TextBlock"/>
</Button.Resources>
<TextBlock Text="FooBar"/>
<Button>
Ok. As it turns out, this is not possible, see Mike Strobel's answer for an explanation of the reason and the rational behind it.
The workaround is to not create an implicit style for TextBlock, because it will affect TextBlocks inside other ControlTemplates.
What works for me is to derive a class from TextBlock, say Label and apply my style to it, and then use it wherever I want a TextBlock with that specific style.
A more "Wpf Natural" way to deal with that is to create a style with a key.
I have a TabControl in which I set the DataContext to an instance of the this class, It's basicly a wrapper for DependencyProperties of a static class with the same properties.
In my Markup I set the DataContext like this
<TabControl DataContext="{Binding ElementName=self, Path=Settings}">
and binding to the property within the TabControl like this
<TextBox Text="{Binding Path=Url, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
However, this does not lead to any updates of the source when the content of the TextBox is changed. I can change the content of the TextBox, let it loose focus etc. it does just not update the source.
Url is a dependency property and when set from XAML, wrapper property setter won't be called.
From MSDN:
The current WPF implementation of its XAML processor is inherently
dependency property aware. The WPF XAML processor uses property system
methods for dependency properties when loading binary XAML and
processing attributes that are dependency properties. This effectively
bypasses the property wrappers. When you implement custom dependency
properties, you must account for this behavior and should avoid
placing any other code in your property wrapper other than the
property system methods GetValue and SetValue.
In case you want to do something on its property changed you should provide PropertyChangedCallback and write code there.
You can refer to the sample here in case PropertyChangedCallback is new to you. Something like:
public static readonly DependencyProperty UrlProperty =
DependencyProperty.Register(
"Url",
typeof(string),
typeof(SettingsWrapper),
new PropertyMetadata(OnUrlChanged)
)
);
private static void OnUrlChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
SettingsWrapper instance = (SettingsWrapper)d;
instance.Settings.Url = e.NewValue.ToString();
}
You said in a (now deleted) comment that your Window has x:Name="self", however the Window class does not have a property called Settings.
If this is an attached property, you need to reference it by the attached property by the full name, and wrap it in parenthesis.
For example,
<TabControl DataContext="{Binding ElementName=self, Path=(local:MyClass.Settings)}">
See WPF Attached Property Data Binding for more info.
In .NET/WP/WPF, I am looking to create my first user control that will render content using a DataTemplate and am wondering how to do that. Do I need to use the Content presenter and pass it a reference to the template or what? Thanks for the help guys!
The basic strategy for including templated content in other fixed content (like the XAML of a UserControl) is to define a set of Content properties (as DependencyProperties) on the containing control and then add a ContentPresenter (with appropriate bindings) as the placeholder into which the content will be injected. In the framework you can see an example of this in HeaderedContentControl which has both a normal Content property set, but also a parallel set of Header properties that are used as a second piece of content.
The properties you can define on your control (differing by platform) are:
Content
ContentTemplate
ContentTemplateSelector
ContentStringFormat
with whatever your custom name is substituted for "Content" in each. In your case you probably only have the first two. Then in your UserControl layout (which is actually defining the Content itself of the UserControl) just place a ContentPresenter and set it up to use your custom properties with the control itself as the Binding Source (ElementName, RelativeSource, or setting the DataContext somewhere to the UserControl itself):
<ContentPresenter Content="{Binding ElementName=MyControl, Path=MyExtraContent}"
ContentTemplate="{Binding ElementName=MyControl, Path=MyExtraContentTemplate}" />
In most cases (but not here) ContentPresenter is used inside a ControlTemplate where you can use a nice shortcut that's built in to bind all the content properties for you:
<ContentPresenter ContentSource="MyExtraContent"/>
You can get the same effect with ContentControl but it's adding extra elements to your visual tree since it's basically just a ContentTemplate containing a ContentPresenter that passes all the properties through. It does allow you to add some visual differences, like Background or Padding, or add a whole custom template but in cases like this you can do exactly the same thing by just adding other controls around your ContentPresenter.
I have a custom Panel where I declared a custom property to hold the content (I don't want to use Children for the content):
[ContentProperty(Name = "PanelContent")]
public class CustomPanel : Panel
{
public static readonly DependencyProperty PanelContentProperty =
DependencyProperty.Register("PanelContent",
typeof(Collection<UIElement>), typeof(CustomPanel),
new PropertyMetadata(new Collection<UIElement>(), null));
public Collection<UIElement> PanelContent
{
get
{
return (Collection<UIElement>)GetValue(PanelContentProperty);
}
}
}
This works perfectly when used like this:
<CustomPanel>
<TextBlock>A</TextBlock>
<TextBlock>B</TextBlock>
</CustomPanel>
But when I want to use the panel as an ItemsPanelTemplate inside ItemsControl, the ContentProperty attribute is ignored and adds everything to the Children collection, not the PanelContent collection:
<ItemsControl ItemTemplate="{StaticResource ReviewTemplate}" ItemsSource="{Binding Reviews}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<CustomPanel></CustomPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
This is not how it should work. According to the documentation:
An ItemsPanelTemplate object element should contain exactly one FrameworkElement-derived class that serves as the root element for items. In most cases this is a Panel-derived class. The expanded template serves as the parent for the realized items and there generally is more than one item. Therefore the XAML content property of the intended root element of an ItemsPanelTemplate should support a collection, as Panel.Children does.
The Panel's GenerateChildren method, that is responsible for this task looks (as seen in ILSpy) like
internal virtual void GenerateChildren()
{
IItemContainerGenerator itemContainerGenerator = this._itemContainerGenerator;
if (itemContainerGenerator != null)
{
using (itemContainerGenerator.StartAt(new GeneratorPosition(-1, 0), GeneratorDirection.Forward))
{
UIElement uIElement;
while ((uIElement = (itemContainerGenerator.GenerateNext() as UIElement)) != null)
{
this._uiElementCollection.AddInternal(uIElement);
itemContainerGenerator.PrepareItemContainer(uIElement);
}
}
}
}
As you can see, it always adds to this._uiElementCollection, which is the field backing the Children property.
I think the ItemsPanelTemplate can only take Panel.Children as the target to layout items. In fact, if you derive your CustomPanel from, say, ContentControl, you will find following exception message in metro style app:
Exception: The ItemsControl.ItemsPanelTemplate must have a derivative of Panel as the root element.
The documentation you linked might be a documentation bug which was copied from WPF time, when you still can insert visuals programmatically into visual tree. In Metro app, there's no longer a way to put your own list of UIElements into visual tree without using Panel.Children.
Because ItemsPanelTemplate is used by ItemsControl to create and use the Panel and it's not XAML that does anything. ItemsControl will simply call Panel.Children.Add no matter what ContentProperty is set to.
Panel is supposed to only layout the children and never style them. So all you must do os only override Arrange and Measure methods. All panels only do that.
For styling and any other logic you must use another approach.