I have a DataTrigger attached to a style for TextBlock, defined as such:
<DataTrigger Binding="{Binding Path=Link, Converter={StaticResource HasContentConverter}}" Value="True">
<Setter Property="TextDecorations" Value="Underline" />
<Setter Property="Cursor" Value="Hand" />
</DataTrigger>
The issue that I'm having is that I have multiple objects that end up using this style, some of which contain a "Link" property, and some of which don't. Whenever the system encounters an object that doesn't, it prints this error in the output window:
BindingExpression path error: 'Link' property not found on 'object' ''DataRowView' (HashCode=53681904)'. BindingExpression:Path=Link; DataItem='DataRowView' (HashCode=53681904); target element is 'TextBlock' (Name=''); target property is 'NoTarget' (type 'Object')
This is expected behaviour, however I'm wondering if there's a way to tell the processor in XAML to only apply if the "Link" property exists (ie. check for the property before attempting to bind, or some other method that doesn't print an error). Is this possible?
Both out of the box and directly it is not possible.
Not out of the box: you can write your own BindingExtension that would behave like that: bind if the prop exists, else ignore. You can also, khem, turn off reporting binding error, but of course that is usually not wanted.
Not directly: you can create an attached attribute of some type, and then set such attribute instead of setting the binding. Your attribute-setter would attach to datacontext-changes and inspect the objects and visual components as they fly around and set the binding or not.
Not directly#2: You can try to "hierarchize" the styles and triggers. As you know, Trigger has a Condition. Split your style in two parts: first is the common style that does not need to be "guarded", and the second one contains features dependent on having "Blargh" property. Set the first style as default/normal. Now create a readonly attached property called "DefinesBlargh" or "HasBlarghDefines" that checks if the target object's datacontext actually has such property. Now add to the first style a trigger that detects whether the styled control has "HasBlarghDefined" equal "true", and in the trigger's action...
...and here's the problem. What to do there? You cannot replace the style again to the second part of the style, as it probably would remove the trigger and in turn deactivate the logic (it would be one-shot). Or, it may simply crash due to the fact of trying to change the style two times in one update sweep. I actually not know what would happend, but I sense "a smell". More over, changing to the second-part would simply erase the common things that the first part set up.
So, if it actually would run and replace the style, you'd have to ENSURE that the original trigger logic and rest of the first style is preserved, I'd suggest using "style inheritace", that is, the based-on style property: http://wpftutorial.net/StyleInheritance.html That is, do not create two separate parts, but rather, make a "base part" with all common things, and a "specialized part" that is based on the first and adds the unsafe extra things. Now dynamically re-replacing to the specialized counterpart is a bit more reasonable.
Or, if you have some control over the layout, you can get smart: Why apply the two styles to the same component? Set the general style on some outer bound of the control and place the extra trigger there, and let the trigger apply the small unsafe second style to the control.
If you really have to target exactly one control with both parts of the style and cannot use "based on" or maybe if it simply does not work etc, you can do another smart trick: use a MultiStyle that allows you to define a style that mergers two/three/+ other styles into one, and then build a trigger hierarchy as follows:
multitrigger
condition: HasBlarghDefined = TRUE
condition: your own data condition
setter: set style = multistyle of "generalpart" and "usnafepart"
multitrigger
condition: HasBlarghDefined = FALSE
condition: your own data condition
setter: set style = just a generalpart
IMHO, that just have to work.
edit: forgot to past the critical link: The MultiStyle
So my final solution for this was to have a base DataGrid class that implements the style in question, minus the "Link" specific data trigger. Then I had a new DataGrid class that derived from my base class, with code to specifically create the data trigger:
Binding binding = new Binding("Link");
binding.Converter = new MDTCommon.Converters.HasContentConverter();
DataTrigger trigger = new DataTrigger();
trigger.Binding = binding;
trigger.Value = true;
Setter setter1 = new Setter(TextBlock.TextDecorationsProperty, TextDecorations.Underline);
Setter setter2 = new Setter(TextBlock.CursorProperty, Cursors.Hand);
trigger.Setters.Add(setter1);
trigger.Setters.Add(setter2);
Style style = FindResource("DefaultStyleInQuestion") as Style;
style.Triggers.Add(trigger);
I was able to use this method because the binding object that had the "Link" property was only used in my derived DataGrid class.
Related
What is ItemContainerTemplate used for? It is derived from DataTemplate, but I don't see any difference between them except the ItemContainerTemplateKey property. When should I use one and when the other?
The only difference between DataTemplate and ItemContainerTemplate is the way the resource dictionary key is automatically provided (assuming it is not set explicitly). Namely, DataTemplate is decorated with [DictionaryKeyProperty("DataTemplateKey")] attribute, and the DataTemplateKey is basically defined as:
public object DataTemplateKey
{
get { return (DataType != null) ? new DataTemplateKey(DataType) : null;
}
See DataTemplate source for reference.
ItemContainerTemplate derives from DataTemplate, but is decorated with [DictionaryKeyProperty("ItemContainerTemplateKey")] attribute (which in practice replaces the inherited one), and ItemContainerTemplateKey property is defined as follows:
public object ItemContainerTemplateKey
{
get { return (DataType != null) ? new ItemContainerTemplateKey(DataType) : null; }
}
See ItemContainerTemplate source for reference.
The difference seems small - DataTemplate returns an instance of DataTemplateKey and ItemContainerTemplate returns an instance of ItemContainerTemplateKey (both derive from TemplateKey). So basically these two are equivalent1:
<ItemContainerTemplate DataType="{x:Type sys:String}" />
<DataTemplate x:Key="{ItemContainerTemplateKey {x:Type sys:String}}" />
and so are these:
<ItemContainerTemplate x:Key="{DataTemplateKey {x:Type sys:String}}" />
<DataTemplate DataType="{x:Type sys:String}" />
The main practical difference between these two is that DataTemplate with default key is treated as an implicit template2, whereas ItemContainerTemplate is not. In fact, you need to manually reference it, e.g.:
<ListBox ItemTemplate="{StaticResource {ItemContainerTemplate {x:Type sys:String}}}" />
I'm not sure about the intentions behind creating ItemContainerTemplate class. I guess it gives you a clearer overview of the code, where you know that such a template is specifically intended to be used in an ItemsControl (or a derived control). Also, I guess it would prove to be pretty simple to write a strongly reusable DataTemplateSelector that would take advantage of this class.
1 They're not equivalent in the sense that created objects are of different types, but functionally they're equivalent.
2 Implicit templates are applied to all objects of corresponding type within the scope, unless a template is set explicitly.
The ItemContainerTemplate describes the world around your Item. For example in a ListBox the selection rectangle around your ListBoxItem. The DataTemplate describes how you ListBoxItem apears and of which elements it consists.
Dr. WPF did a good example:
http://drwpf.com/blog/category/item-containers/
You can put an ItemContainerTemplate in a ResourceDictionary, and it will automatically use the DataType as its key.
That's the only difference.
ItemContainerTemplate is useful/necessary when you need different Item containers for an ItemsControl.
Usually the XAML infracstructure decides which item container is used for a given ItemsControl:
ListBox uses ListBoxItem
DataGrid uses DataRow
ComboBox uses ComboBoxItem
Menu uses MenuItem
As for a Menu you would sometimes want a Separator (which is not a MenuItem technically)
That's where ItemContainerTemplate, ItemContainerTemplateSelector and ItemContainerTemplatekey come into play.
Based on the viewmodel/datacontext type or one/many of its property values you can switch between a Separator in an ItemContainerTemplate and a MenuItem in another
ItemContainerTemplate.
You may use triggers or a ItemContainerTemplateSelector to achieve this.
Actually and honestly I am myself just now about to understand what the ItemContainerTemplateKey is for.
I think to have understood that it's an easy way to map an ItemContainerTemplate to a data type without the need for a Selector or code behind or triggers.
If you're fine with the default ItemContainerTemplate you simply don't need to deal with it in your XAML. Manipulating the ItemsContainer style can be achieved within the ItemContainerTemplate. Having a custom DataTemplate to bind your data (and also to style it) is done within the ItemTemplate.
Usage of ItemContainerTemplate is rarely needed. But sometimes very handy.
You could check that Link to see the difference between controltemplate and datatemplate and hierarchicaldatatemplate itemspaneltemplate:
http://nirajrules.wordpress.com/2009/03/08/controltemplate-vs-datatemplate-vs-hierarchicaldatatemplate-vs-itemspaneltemplate/
I have a big project at hand which involves a large amount of views and usercontrols. Additionaly, I want to set the FontFamily of every element to a certain font.
This works with most of the usercontrols like textBlocks, buttons and labels. Sadly this does not hold for textBoxes. They remain unchanged.
Before I create the whole GUI, I am overriding most of the metadata for elements containing text:
TextElement.FontFamilyProperty.OverrideMetadata(typeof(TextElement),
new FrameworkPropertyMetadata(new FontFamily("Calibri")));
TextBlock.FontFamilyProperty.OverrideMetadata(typeof(TextBlock),
new FrameworkPropertyMetadata(new FontFamily("Calibri")));
After a bit of searching, I found this article using the same method: http://blog.davidpadbury.com/
It clearly states at the end:
"In the above image you’ll see that we’ve successfully change the font on the text blocks, labels and buttons. Unfortunately the font inside the TextBox remains unchanged, this is due to it receiving it’s FontFamily property from it’s base class Control. Control adds itself as an Owner of the TextElement FontFamilyProperty but specifies it’s own metadata which we are then unable to override."
It also suggests to create a control template, which then sets the fontFamily. Is there another way? I want to set the fontFamily programmatically at the start without using XAML or creating a controlTemplate and using it as a base template for every textBox.
Thanks in advance.
You can declare a Style without the x:Key property and it will apply to all controls of that type:
<Style TargetType="{x:Type Control}">
<Setter Property="FontFamily" Value="Calibri" />
</Style>
Alternatively, you can simply set this on the MainWindow definition which will affect most elements:
<Window TextElement.FontFamily="Calibri" ...>
...
</Window>
Ahhh... I've just noticed your condition of not using xaml... sorry, I should have looked closer the first time.
UPDATE >>>
After a little research, it seems that you can do this in code like this:
Style style = new Style(typeof(TextBlock));
Setter setter = new Setter();
setter.Property = TextElement.FontFamilyProperty;
setter.Value = new FontFamily("Calibri");
style.Setters.Add(setter);
Resources.Add(typeof(TextBlock), style);
Unfortunately, you'd have to do other Styles for other types of controls too.
UPDATE 2 >>>
I just thought of something... that previous example just set the Style into the local Resources section which would be out of scope for your other modules. You could try setting the Style to the Application.Resources section which has global scope. Try replacing the last line of the code example above to this:
App.Current.Resources.Add(typeof(TextBlock), style);
This question is for WinRT, but may also be applicable for Silverlight. Say I have databound the Background property of a ListView/ListBox, but I want a that databinding only to be in place when a particular theme is applied. I've implemented themes using Merged Dictionaries of XAML styles. When a different theme is applied, I want it to be statically defined by the style.
Is there a way to achieve this using XAML only?
I've tried placing the "Style" attribute after "Background" in the ListView tag itself, to see if the order of the properties mattered, but that did not seem to have any effect.
Nilzor -
This seems like the kind of place where you would use a Custom Converter. In this way, when the binding happens you can run logical tests and any arbitrary code to return the a value that is acceptable for binding.
WinRT project come with an example of the custom converter which i believe is named BooleanToVisibility Converter.
For the record: This does not NEED to be a conversion (i.e. bound object is bool, convert to Visibility and return it to the Visibility property) it can be a logical test -- The bound object is XYZ derives from ABC & if XYZ.Parent.SomeProperty == someValue return different ABC.
Here is a stack overflow link for Creating / Implementing them:
Binding to a property of a custom converter
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).
I have a set of controls bound to data, on which I would like to programmaticaly add validators to the bindings. Currently I'm able to iterate over the visual tree to find those controls with bindings, and also add my validators to these controls. But to further specify which controls should have specific validation I wanted to use styles. So my XAML looks like this:
<TextBox Name="someTextBox" Style="{StaticResource optionalNumericTextBox}" />
Here, the optionalNumericTextBox style serves both adding a validation error template and as a decorator to indicate that this textbox should have the optional numeric validator applied.
The problem occurs when I'm traversing the visual tree, discovers a control with bindings, and then need to determine the style in use. Currently I've tried
dependencyObject.GetValue(FrameworkElement.StyleProperty)
which gives me a Style object but as far as I can tell, this object does not carry the
'optionalNumericTextBox' value. Is it even possible to determine the key or is this information lost in the XAML reader?
When using StaticResourceExtension, this information is lost at compile time when converting your XAML to BAML. Using DynamicResourceExtension, on the other hand, keeps the key around so the resource can be resolved at runtime. To get at the key, you'll need to use ReadLocalValue():
//this gets the Style
var s = textbox.GetValue(TextBox.StyleProperty);
//this gets a ResourceReferenceExpression
var l = textbox.ReadLocalValue(TextBox.StyleProperty);
The problem is, ResourceReferenceExpression is an internal type, so you'll need to use reflection to pull out the key.
As an alternative to all this, have you considered hijacking the Tag property instead?
<Style x:Key="optionalNumericTextBox" TargetType="TextBox">
<Setter Property="Tag" Value="optionalNumericTextBox"/>
</Style>
Then your code can simply check the Tag property on the TextBox.