Path.Data styling works only on first instance of styled object - wpf

A have a ListBox of items, every ListBoxItem contains an icon in the form of a Path object, like so:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid ...>
...
<Path Margin="4" Style="{StaticResource ErrorIconPath}"
Stretch="Uniform" Width="26" Height="26"
RenderTransformOrigin="0.5,0.5" Grid.Column="1" Grid.Row="1"
UseLayoutRounding="False"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
The Path's style is contained in Appl.xaml (Application.Resources section) and is the following:
<Style x:Key="ErrorIconPath" TargetType="Path">
<Setter Property="Data" Value="F1M874.094,289.369L854.3,254.63C854.028,254.151 853.515,253.856 852.958,253.856 852.403,253.856 851.89,254.151 851.617,254.63L831.824,289.369C831.555,289.84 831.559,290.416 831.835,290.883 832.111,291.348 832.618,291.634 833.165,291.634L872.752,291.634C873.299,291.634 873.805,291.348 874.081,290.883 874.357,290.416 874.361,289.84 874.094,289.369 M855.653,287.189L850.264,287.189 850.264,282.745 855.653,282.745 855.653,287.189z M855.653,279.41L850.264,279.41 850.264,266.077 855.653,266.077 855.653,279.41z" />
</Style>
The trouble is that only the first item in the ListBox binds the Data property as expected, the other ones don't bind it at all (hence they appear as blank space, but match the size of the Path). Also when I use the style anywhere else (i.e. outside the ListBox), only the first instance that occurs will bind.
The weird thing is that if I define for example the Fill property in the Style instead of inline, it works just fine and doesn't exibit the same problems as the Path property.
My guess is that is has something to do with Data not being a primitive type, but I haven't found any fixes.
EDIT: Interestingly, when I bind the Data property directly to System.String resource, it works. I would still like to be able to define this property via a Style though.
EDIT 2: I've just came across the same issue in WPF, when setting Path to a Content of a Button via a Style that is used across more buttons. The path shows up in just one buttons, the others are blank.

Path.Fill is a DependencyProperty, while Path.Data isn't. Instead do:
<DataTemplate>
<Grid ...>
...
<ContentPresenter Content="{StaticResource MyPath}"/>
</Grid>
</DataTemplate>
ContentPresenter.Content is a DependencyProperty so this should work:
<Path x:Key="MyPath" Margin="4" Style="{StaticResource ErrorIconPath}"
Stretch="Uniform" Width="26" Height="26" VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5" Grid.Column="1" Grid.Row="1"
UseLayoutRounding="False" HorizontalAlignment="Center"
Data="F1M874.094,289.369L854.3,254.63C854.028,254.151 853.515,253.856 852.958,253.856 852.403,253.856 851.89,254.151 851.617,254.63L831.824,289.369C831.555,289.84 831.559,290.416 831.835,290.883 832.111,291.348 832.618,291.634 833.165,291.634L872.752,291.634C873.299,291.634 873.805,291.348 874.081,290.883 874.357,290.416 874.361,289.84 874.094,289.369 M855.653,287.189L850.264,287.189 850.264,282.745 855.653,282.745 855.653,287.189z M855.653,279.41L850.264,279.41 850.264,266.077 855.653,266.077 855.653,279.41z"/>

I am guessing that Geometry cannot be shared. Have you tried setting the x:Shared= "false" to:
<Style x:Key="ErrorIconPath" TargetType="Path">

I've experienced the same behavior in Silverlight and asked a similar question here on StackOverflow.com
( https://stackoverflow.com/q/13426198/1796930), but as I'm writing this, it's been 1 month and I've yet to get even a single answer.
However, as you mentioned in your first edit, I too was able to perform a workaround by creating a resource with my geometry data as a string and then binding the Data property of the Path objects to the string resource resource.
I also had to create two instances of the Path objects that were identical other than each one using a different resource (i.e. two different icons) and then binding the visibility of each to a property in my ViewModel to display the appropriate one.

I am very sure that you did not forgot the stroke here in Path style
<Setter Property="Stroke" Value="Red"/>
I have tested you code on my machine , it worked fine if above line added in style

My first tought was your Path would be broken or not valid. But then I saw you are using the Syncfusion Metro Studio. I tried it with exactly the same code you have and it worked very well. In a Data Template of 5 Items or as a single Path Item.
Have you tried to set the Fill statically to Red or something?
Also maybe try this for the Style definition
<Style x:Key="ErrorIconPath" TargetType="{x:Type Path}">
Third suggestion would be to move the style definition from the App to your Page or even to your Control itself.
To be sure there will be no default styles applied, try
OverridesDefaultStyle="True"
Hope this helps :)

Related

Expander-like WPF control that only hides empty children

In my WPF view, I need something similar to an Expander or a TreeView, but instead of completely hiding the content, I only want to hide empty parts, i.e. TextBoxes with null or empty text, or empty ItemCollections.
I thought about using a style with a DataTrigger or set Visibility with a converter, but how would I link that to the parent's setting (e.g. IsExpanded)?
I would like to avoid doing this in the ViewModel, as that would need a property for each section (and I need lots of them), but it's purely visual and therefore IMHO it only belongs to the View.
So I guess the way to go is to use DependencyProperties or write some CustomControls, but I don't have an idea where to start. The XAML of the end result could look something like this:
<CustomExpander Header="Main" CollapseContentIfEmpty="True">
<CustomExpander Header="Section1" CollapseContentIfEmpty="True">
<StackPanel>
<TextBox Text="{Binding SomeString}" />
<TextBox Text="{Binding SomeEmptyString}" />
</StackPanel>
</CustomExpander>
<CustomExpander Header="Section2" CollapseContentIfEmpty="True">
<ListView ItemsSource="{Binding SomeCollectionView}" />
</CustomExpander>
</CustomExpander>
In this example, if CollapseContentIfEmpty is set to true and the CollectionView shows no elements (e.g. due to filters), only the content of SomeString should be visible, along with all the headers. If SomeString is empty, only "Main" should be visible, as now all child CustomExpanders are empty as well.
Setting CollapseContentIfEmpty to false (e.g. via a Button like in Expander) would show all Children again, regardless if they are empty or not.
I thought about using a style with a DataTrigger or set Visibility with a converter, but how would I link that to the parent's setting (e.g. IsExpanded)?
Use a binding with a {RelativeSource}.
In the following example, the TextBlock is invisible unless you set the Tag property of the parent UserControl to true:
<UserControl xmlns:sys="clr-namespace:System;assembly=mscorlib">
<UserControl.Tag>
<sys:Boolean>false</sys:Boolean>
</UserControl.Tag>
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<StackPanel>
<TextBlock Text="text..."
Visibility="{Binding Tag,
RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel>
</UserControl>
You can of course replace the UserControl with a custom control with a custom bool property.
An Expander collapses its entire Content which is different from hiding specific controls in that content.

WPF Binding up and down the Visual Tree

From a control in a WPF XAML view, I need to access the properties of another control that I can reach in the Visual Tree only when walking up to a common parent control and then down from there.
As an example:
<PageUserControl>
<Grid>
<TextBlock Text="Some example text" />
</Grid>
<TextBlock Text="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType=PageUserControl, Path=??? I want to access the TextBlock}" />
</PageUserControl>
I want to access the text property of the first text block from the second text block (this is just an example).
What I would need is a way to combine relative sources, first one to go up the visual tree and find the PageUserControl, second one to go down the visual tree from there and find the grid and then finally a third one to find the text block within the grid.
Am I missing something here or is it just not possible?
I cannot add control IDs or something like this, it has to work with control types only.
I was thinking about something like a relative source that takes a XPath syntax, but it seems as if this was meant for another purpose (binding XML documents).
Maybe another idea?
Thank you!
I found a solution for my problem. It is possible using this approach:
<PageUserControl>
<Grid>
<TextBlock Text="Some example text" />
</Grid>
<TextBlock Text="{Binding Path=Children[0].Children[0].Text,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PageUserControl}}" />
</PageUserControl>
While not very flexible, it is good enough for me.

Static resource array binding only works for the first time

I created a custom control which inherits from toolbar.
I would like that the default control template of the toolbar will contain a couple of default buttons.
In order to achieve this, I created a static array to hold the button list:
<x:Array x:Key="toolbarButtons" Type="{x:Type ToggleButton}">
<ToggleButton Content="Bold"
Command="{x:Static ns1:EditingCommands.Bold}"
CommandTarget="{Binding}"
IsChecked="{Binding IsBold, Mode=TwoWay}"/>
<ToggleButton Content="Italic"
Command="{x:Static ns1:EditingCommands.Italic}"
CommandTarget="{Binding}"
IsChecked="{Binding IsItalic, Mode=TwoWay}"/>
</x:Array>
The toolbar control has datacontext which is bounded to a text editor which includes all of the command bindings and the boolean dependency properties (IsBold, IsItalic).
I set the Toolbar ItemSource to use the array like this:
<Setter Property="ItemsSource" Value="{StaticResource toolbarButtons}"/>
Now, when I open a window who hosts the toolbar for the first time on a given run, everything works great.
The problem is, when I close the window, and reopen it, the button bindings stop working (IsCheked property stops being connected to the dependency property).
I used snoop to check the bindings, and it says that the value of IsChecked is local, which means the binding is ignored.
I suspect that the problem is my array is a static resource, so the toolbar uses the same instance from time to time, and this somehow ruins the binding.
My question is how to solve this, or maybe should I use a different approach in order to achieve default buttons for my toolbar?
I think the problem is that when you declare your array in XAML, it's only instantiated once. So, the second toolbar (and on) are trying to use the exact same objects. When it's reused, your bindings are likely being overwritten.
Have you tried adding x:Shared="False" to the array declaration?
<x:Array x:Key="toolbarButtons" x:Shared="False" Type="{x:Type ToggleButton}">

Skinning Control Backgrounds - Better Performance?

sorry if this question is overly simple, but I'm having a hard time figuring out how to create backgrounds to controls - in the hopes that it will improve app performance.
I have 9 different controls. All of them have a background. The backgrounds are made up of either images, other controls or both. All of those backgrounds have another background.
Think of this like Power Point with slides, slide layouts and slide masters - inherited in that order. I have 9 slides / controls.
The first 3 controls have the same "control layout" (let's call it
ControlLayout1). ControlLayout1 gets some of it's elements from ControlMaster1.
The second 3 controls also have the same control layout, but it is
different from the first. Let's call it ControlLayout2. It also
inherits from ControlMaster1.
The final set of 3 controls are different again. We can call them
ControlLayout3. But this time, they inherit from a different master - ControlMaster2.
Right now in each control I'm writing out all the XAML each time separately. I'm thinking there must be a way to not write these in each of these each item. Ideally, what I would like to create is one set of XAML that can be reused.
Here's some pseudo-XAML:
<UserControl x:Name="Control1">
<MyBackground (ControlLayout1)/>
</UserControl>
<UserControl x:Name="Control2">
<MyBackground (ControlLayout2)/>
</UserControl>
<UserControl x:Name="Control3">
<MyBackground (ControlLayout3)/>
</UserControl>
And then somewhere for ControlLayouts (I don't know, like Application.Resources or elsewhere)
<Canvas x:Name="ControlLayout1">
<MyMasterBackground (ControlMaster1)/>
</Canvas>
<Canvas x:Name="ControlLayout2">
<MyMasterBackground (ControlMaster1)/>
<TextBox Text="The Control 2">
</Canvas>
<Canvas x:Name="ControlLayout3">
<MyMasterBackground (ControlMaster2)/>
<TextBox Text="The Control 3">
</Canvas>
And then for the ControlMasters
<Canvas x:Name="ControlMaster1">
<Canvas.Background>
<ImageBrush ImageSource="/Images/image1.jpg" />
</Canvas.Background>
</Canvas>
<Canvas x:Name="ControlMaster2">
<Canvas.Background>
<ImageBrush ImageSource="/Images/image2.jpg" />
</Canvas.Background>
<TextBox Text="Control Master 1">
</Canvas>
Once defined, the ControlLayouts and ControlMasters never need to change - they are static.
Beyond just having a smaller XAP if I can put these all in one location and reuse the XAML, I'm hoping performance will be improved in my app as the ControlLayouts automatically get BitmapCached or something like that.
So first, is there a good strategy to implement the above (the ControlLayouts and Masters do not have any code-behind)? Secondly will performance be improved in loading of Control1, Control2, etc.? Finally, if they were pure usercontrols (i.e. they had some code behind), would that be better for performance?
Thanks in advance!
What you ask for is a combination of a few things:
About the Background thing: just create a dependency property (let's call it MyBackgroundDP) of type Brush in the code behind of a UserControl, and bind it to your XAML like:
<UserControl ...>
<Grid Background={"Binding MyBackgroundDP, RelativeSource={RelativeSource Mode=FindAncestor, AncestoryType=UserControl}}">
<!-- More XAML declarations -->
</Grid>
</UserControl>
To create the dependency property, you can use the built in snippet in visual studio: propdp
Simply write "propdp" and that TAB twice. Fill up the fields and it's all good.
Alright so that was easy enough, right? ;)
Now the tougher part: making so-called master pages.
Actually it's not that much different from the background thing.
Declare another dependency property, only this time of type object, or FrameworkElement (better).
Then in your XAML, you declare a kind of placeholder: ContentControl. Let's call it MyContentDP for this example:
<UserControl ...>
<Grid Background={"Binding MyBackgroundDP, RelativeSource={RelativeSource Mode=FindAncestor, AncestoryType=UserControl}}">
<ContentControl ContentTemplate="{Binding MyContentDP, RelativeSource={RelativeSource Mode=FindAncestor, AncestoryType=UserControl}}" />
</Grid>
</UserControl>
You can then fine tune whatever else you want to provide in this "master view", add a border around the Grid, put some flowers, you name it.
Once you're done, this is how you use it, assuming it was called MyUserControl
<Window ...
xmlns:local="...reference_to_your_usercontrol_dll/exe">
<Grid>
<local:MyUserControl MyBackgroundDP="Red">
<local:MyUserControl.MyContentDP>
<!-- More XAML declarations here -->
</local:MyUserControl.MyContentDP>
</local:MyUserControl>
</Grid>
</Window>
Now the performance point:
If you put all the XAML for this as a Custom control (which is DIFFERENT from a UserControl), you can then put all the XAML in your App.xaml
Why? because parsing XAML can be an intensive operation, and if you make WP7/SL parse it at runtime whenever you need it, you lose performance.
Instead, your App.xaml gets parsed at startup, then it's in memory. That's what's done in the loading of your application. You would get a performance boost, although it would be minimal for controls made of few XAML, it is still a good practice.
Hope this helps,
Bab.

How to reuse icons (xaml-paths) in a large project?

I am part of the development of a larger-scale Silverlight 4 project, where we will have a set of symbols that should be used across different parts of the GUI (see the example below).
These icons are made from multiple paths directly in Blend, and will be used, either singly or as different visual states in usercontrols (with the same icon used in more than one context). In order to facilitate changing the design of a single icon, and having it propagate throughout the application, what is the best way to store these?
I have tried creating styles from them (right click -> edit style..), but this only allows me to create an empty style, without any path data. manually putting the xaml code for the grid containing the paths into a dictionary hasn't helped either, what am I missing?
How do I save the path and style (colour, stroke, fill, etc) information in an easy way, preferably in a resource dictionary, so I can easily reuse them in usercontrols and elsewhere, while maintaining the easy updating?
Example of the icons I'm trying to reuse:
<Style x:Key="MyIcon" TargetType="ContentControl">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Path Stretch="Fill" Fill="Red" Data="F1 M 24,13C 27.1521,13 29.9945,14.3258 32,16.4501L 32,11L 35,14L 35,22L 27,22L 24,19L 29.5903,19C 28.217,17.4656 26.2212,16.5 24,16.5C 20.1969,16.5 17.055,19.3306 16.5661,23L 13.0448,23C 13.5501,17.3935 18.262,13 24,13 Z M 24,31.5C 27.8031,31.5 30.945,28.6694 31.4339,25L 34.9552,25C 34.4499,30.6065 29.738,35 24,35C 20.8479,35 18.0055,33.6742 16,31.5499L 16,37L 13,34L 13,26L 21,26L 24,29L 18.4097,29C 19.783,30.5344 21.7787,31.5 24,31.5 Z "/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
And then use style with contentcontrol type (or derivates):
<ContentControl Style="{StaticResource MyIcon}" Width="20" Height="20" Grid.Row="0"/>
All styles can be inside some resource dictionary:
This is quite informative article:
http://blogs.infosupport.com/tips-for-effective-usage-of-resource-dictionaries-in-silverlight-and-wpf/

Resources