Per-item converter instance in style of treeview item container - c#

I have ItemContainerStyle set for TreeView and use MultiBinding with converter in it:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected">
<Setter.Value>
<MultiBinding Converter="{StaticResource SelectedCategoryConverter}" Mode="TwoWay">
<Binding Path="."/>
<Binding Path="CurrentCategoryId" RelativeSource="{RelativeSource AncestorType=UserControl}"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
I need SelectedCategoryConverter to be created for every unique item in the tree view, so I declared it with x:Shared="False" in window resources:
<local:SelectedCategoryConverter x:Shared="false" x:Key="SelectedCategoryConverter"/>
but it doesn't help: only one instance of converter is created when 2 or more items passed to TreeView through ItemsSource. I tried to write converter as MarkupExtension, but it didn't help too.

I think there is only one way.
1. Move ItemContainerStyle to the resource and marked it as nonshared:
<Application.Resources>
<Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}" x:Shared="False">
<Setter Property="IsSelected">
<Setter.Value>
<MultiBinding Converter="{local:SelectedCategoryConverterCreator}" Mode="TwoWay">
<Binding Path="."/>
<Binding Path="CurrentCategoryId" RelativeSource="{RelativeSource AncestorType=UserControl}"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="IsExpanded" Value="True" />
</Style>
</Application.Resources>
<TreeView ItemContainerStyle="{StaticResource TreeViewItemStyle}">
Create your own MarkupExtension which will create a new instance of SelectedCategoryConverter:
public class SelectedCategoryConverterCreatorExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new SelectedCategoryConverter();
}
}
It will create a new instance of SelectedCategoryConverter for each item. But remember that it is not very memory efficient.

Related

Implementing font family from resource in WPF

I am using WPF,C#,XAML
and I wanted to implement a new font family.
the font has been downloaded from the internet and it is a file called:
"LiberationMono-Regular.ttf"
I opened a "Font" folder and add the file into it.
I would like to implement this font into a specific style.
I try to implement it does not work for me.
attaching the code I wrote will appreciate your help
Thanks.
<Window.Resources>
<FontFamily x:Key="DataFont">LiberationMono-Regular.ttf"</FontFamily>
</Window.Resources>
<Style TargetType="syncfusion2:GridCell" x:Key="ComCellData">
<Setter Property="Foreground" Value="{Binding COMPort , Converter={StaticResource CVconverters } }" />
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource BookMarkAndSelectedMultiConverter}">
<Binding Path="isBookMarked"></Binding>
<Binding Path="isSelected"></Binding>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="FontFamily" Value="{StaticResource DataFont}"></Setter>
</Style>
You should reference the custom font family by its name:
<FontFamily x:Key="DataFont">/#Liberation Mono</FontFamily>
The above markup assumes that you have added the LiberationMono-Regular.ttf to the root of your project and set its Build Action to Resource.

Set style for specific item type in ItemsControl

I have an ItemsControl, which I use to draw two different sets of shapes on a canvas. As such, I have two ItemsSource containing Edge objects and Node objects.
I have two different DataTemplates for each type. However, I need to set the canvas positioning for the nodes, but not for the edges. There are abundant examples on the internet on how to do this with a single ItemsSource, but not with multiple as in my case.
I have hacked it like this, but this throws a lot of binding errors in the output window (because only Nodes have a Position property, not Edges, and thus this 'works'). Also, I want to set the ZIndex for the Nodes and Edges separately, which is impossible in this way. Does anyone have any suggestions?
<ItemsControl>
<ItemsControl.ItemsSource>
<MultiBinding>
<MultiBinding.Converter>
<p:CompositeCollectionConverter/>
</MultiBinding.Converter>
<Binding Path="Graph.Nodes"/>
<Binding Path="Graph.Edges"/>
</MultiBinding>
</ItemsControl.ItemsSource>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type model:Edge}">
<Path
Stroke="Blue"
Data="{Binding Path=EdgeSegments, Converter={StaticResource EdgeSegmentsConverter}}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type model:Node}">
<Ellipse
Width="8"
Height="8"
Stroke="Black"
Fill="Gray"/>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left">
<Setter.Value>
<Binding Path="Position.X">
<Binding.Converter>
<p:NodePositionConverter />
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Top">
<Setter.Value>
<Binding Path="Position.Y">
<Binding.Converter>
<p:NodePositionConverter />
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Why not use ItemContainerStyleSelector? Add the styles to ItemsControl.Resources:
<Style TargetType="ContentPresenter" x:Key="{x:Type model:Edge}">
<Setter Property="ZIndex">
...
</Setter>
</Style>
<Style TargetType="ContentPresenter" x:Key="{x:Type model:Node}">
<Setter Property="ZIndex">
...
</Setter>
<Setter Property="Canvas.Top">
...
</Setter>
<Setter Property="Canvas.Left">
...
</Setter>
</Style>
Note the x:Key is set to the type so we can easily look up by item.GetType() in the style selector:
public override Style SelectStyle(object item, DependencyObject container) {
var containerElement = (FrameworkElement)container;
var style = containerElement.TryFindResource(item.GetType()) as Style;
if (style != null) {
return style;
}
return base.SelectStyle(item, container);
}
It's a bit weird what you are doing.. I didn't know if your really need to do it with this way...
You can't juste merge your two List into a simple List ? Like this:
List<AChild> a;
List<BChild> b;
List<Mother> ab = a.Concat(b).Cast<Mother>();
And after in your View you can use a TemplateSelector who will help you to choose which DataTemplate is appropriate for the item.
<ItemsControl ItemTemplateSelector="{StaticResource YourTemplateSelector}" ItemsSource="{Binding ab}"/>
The answer is given by Rachel in the following StackOverflow thread: https://stackoverflow.com/a/7931448/970589
So kudos to her. Short summary of the answer:
Use a converter to check the type of the calling binding, and return a value according to that type.

WPF DataGridTextColumn binding and style

here's the deal: styling the DataGridTextColumn's textblock for a datagrid.
I need to format the textblock by it's value, by comparing it from another binded value.
What i want to achieve is something like this:
<Style x:Key="ExpeditionerCellStyle" BasedOn="{StaticResource RightAlignStyle}" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=BEST}" Value="{RelativeSource Mode=Self}">
<Setter Property="Background" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
Which is not possible, because the Value of Datatrigger cannot be a relative source.
So i tried with multibinding
<Style x:Key="ExpeditionerCellStyle" BasedOn="{StaticResource RightAlignStyle}" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsValueEqualParameterConverter}">
<Binding Path="BEST" />
<Binding RelativeSource="{RelativeSource Mode=Self}" />
</MultiBinding>
</DataTrigger.Binding>
</DataTrigger>
</Style.Triggers>
</Style>
with no luck: textblock Text property is empty, probably because the style is applied before the actual binding is performed.
I have no more ideas.
Please help me!
I'm not sure if this will work, but I noticed that you forgot to specify the Binding.Path in your MultiConverter example... try this:
<Style x:Key="ExpeditionerCellStyle" BasedOn="{StaticResource RightAlignStyle}" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsValueEqualParameterConverter}">
<Binding Path="BEST" />
<Binding Path="Text" RelativeSource="{RelativeSource Mode=Self}" />
</MultiBinding>
</DataTrigger.Binding>
</DataTrigger>
</Style.Triggers>
</Style>
If you only want to change the style of the textblock based on the content of the textblock why not just add a bidning to the textblock background and use a converter to check the content of the text?
In other words bind both the text and the background to the same source and use a converter with the background and in the converter match the content and return the correct background style.
If you need something else leave a comment so we better know what the problem is.
I ended modifying a bit my code.
Instead of relying on the value of the cell, in the field "BEST" i passed the name of the column.
Then using the converter I check
Current textblock (as suggested by #Sheridan) -> DatagridCell -> Column's header
if it match, i change the style on this textblock.
Thanks everyone!!!

Change DataTrigger binding reference at runtime

I've a ListView with this DataTrigger attached:
<Style x:Key="HideShowStyle" TargetType="{x:Type ListViewItem}">
<Style.Resources>
<localConverters:ShowHideConverter x:Key="ShowHideConverter" />
</Style.Resources>
<Style.Triggers>
<DataTrigger Value="true">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource showHideConverter}">
<Binding Path="EndingDate" />
<Binding Path="UserName" />
<Binding ElementName="SearchBox" Path="Text" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Value="false">
(...)
</DataTrigger>
</Style.Triggers>
</Style>
I use this in a hardcoded scenario to hide/show some ListItems in the ListView. The binding source is specified in the ElementName tag, and the TextBox referred is declared a few lines before.
Now, I've the necessity to change that Binding at runtime with another field incapsulated in a istantiated class. I've tried to use WPF class instead of XAML, but nothing, I don't have any clue how to obtain this.
I'm open to any advice! :)
var style = (Style)FindResource("HideShowStyle");
var trigger = (DataTrigger)style.Triggers[0];
var multibinding = (MultiBinding)trigger.Binding;
var binding1 = (Binding)multibinding.Bindings[0];
var binding2 = (Binding)multibinding.Bindings[1];
var binding3 = (Binding)multibinding.Bindings[2];
Change bindings at will.

Value Converter not getting called when data changes

I'm working on a user control right now in which I have a path as part of the control. There are 3 possible paths I might want to display based on the values of certain data. To determine which Path I want to use, I have a value converter that takes in the data and returns a number to represent which of the paths I should use.
My first thought was to just use the property changed callback from the two dependency properties I am getting data from, but those callbacks must be static and the XAML code is always non-static.
My second attempt is to now use datatriggers with the value converter described above. Below is the code I have.
<Path x:Name="path" Stretch="Fill" Width="111.75" Height="118.718" Data="F1M205.917,103.0088C189.333,93.8108,170.128,88.9998,150,88.9998C129.873,88.9998,110.584,93.8108,94.167,102.8408L116.1,144.2508L150,208.7178L183.9,144.2508z" Canvas.Left="0" Canvas.Top="0">
<Path.Resources>
<Style TargetType="{x:Type Path}">
<Style.Triggers>
<DataTrigger Value="-1">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ToleranceRangeTypeChecker}">
<Binding ElementName="UserControl" Path="ToleranceZoneLowerBound" />
<Binding ElementName="UserControl" Path="ToleranceZoneUpperBound" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Data" Value="F1M205.917,103.0088C189.333,93.8108,170.128,88.9998,150,88.9998C129.873,88.9998,110.584,93.8108,94.167,102.8408L116.1,144.2508L150,208.7178L183.9,144.2508z" />
<Setter Property="Width" Value="111.75" />
<Setter Property="Height" Value="118.718" />
<!--<Setter Property="Canvas.SetLeft"-->
</DataTrigger>
<DataTrigger Value="0">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ToleranceRangeTypeChecker}">
<Binding ElementName="UserControl" Path="ToleranceZoneLowerBound" />
<Binding ElementName="UserControl" Path="ToleranceZoneUpperBound" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Data" Value="F1M150,88.9998C129.873,88.9998,110.584,93.8108,94.167,102.8408L150,208.7178C150,208.7178,150,114.157407529625,150,88.9998z" />
<Setter Property="Width" Value="55.917" />
<Setter Property="Height" Value="118.718" />
<!--<Setter Property="Canvas.SetLeft"-->
</DataTrigger>
<DataTrigger Value="1">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ToleranceRangeTypeChecker}">
<Binding ElementName="UserControl" Path="ToleranceZoneLowerBound" />
<Binding ElementName="UserControl" Path="ToleranceZoneUpperBound" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Data" Value="F1M205.917,103.0088C189.333,93.8108 170.128,88.9998 150,88.9998 150,113.365662567029 150,208.7178 150,208.7178L183.9,144.2508z" />
<Setter Property="Width" Value="111.75" />
<Setter Property="Height" Value="118.718" />
<!--<Setter Property="Canvas.SetLeft"-->
</DataTrigger>
</Style.Triggers>
</Style>
Another thought I had was to instead actually have 3 different paths and use setters to change the visibility of each, but I think that having one path and changing it's propertise would be more logical. I would also prefer one path because my goal would be to eventually animate between the paths rather than have them change instantly.
Thanks!
You can get the instance by casting the sender parameter in the property change callbacks.

Categories