I have strange behavior with the ContentPresenter in WPF. I use the ContentPresenter control in my user control to change some child controls based on a property change in my view model. The ContentPresenter definition looks like this:
<ContentPresenter Grid.Row="2" Content="{Binding}">
<ContentPresenter.Resources>
<DataTemplate x:Key="Gray" d:DataType="{x:Type vm:ResultViewModel}">
<util:RestrictDesiredSize KeepHeight="True">
<Border Padding="5,5">
<controls:GrayAnalysis/>
</Border>
</util:RestrictDesiredSize>
</DataTemplate>
<DataTemplate x:Key="Color" d:DataType="{x:Type vm:ResultViewModel}">
<util:RestrictDesiredSize KeepHeight="True">
<Border Padding="5,5">
<controls:ColorAnalysis/>
</Border>
</util:RestrictDesiredSize>
</DataTemplate>
</ContentPresenter.Resources>
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="ContentTemplate" Value="{StaticResource Gray}" />
<Style.Triggers>
<DataTrigger Value="True" Binding="{Binding Unit.IsColor.Value}">
<Setter Property="ContentTemplate" Value="{StaticResource Color}" />
</DataTrigger>
<DataTrigger Value="False" Binding="{Binding Unit.IsColor.Value}">
<Setter Property="ContentTemplate" Value="{StaticResource Gray}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
So in general I have two templates and when the "Unit.IsColor.Value" property changes the corresponding data template should be applied. This seems to work in the first place.
When the correct data template was applied, another property change in the view model (for a different property) which is only bound to a control inside the applied template, the complete data template will be created again.
So instead of applying the data template only one time and then changing the control inside the data template, the ContentPresenter will recreate the complete data template for every property change.
The callstack looks like this:
mscorlib.dll!System.Activator.CreateInstance(System.Type type, bool nonPublic) Unknown
mscorlib.dll!System.RuntimeType.CreateInstanceImpl(System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, object[] args, System.Globalization.CultureInfo culture, object[] activationAttributes, ref System.Threading.StackCrawlMark stackMark) Unknown
mscorlib.dll!System.Activator.CreateInstance(System.Type type, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, object[] args, System.Globalization.CultureInfo culture, object[] activationAttributes) Unknown
mscorlib.dll!System.Activator.CreateInstance(System.Type type, object[] args) Unknown
System.Xaml.dll!System.Xaml.Schema.SafeReflectionInvoker.CreateInstanceCritical(System.Type type, object[] arguments) Unknown
System.Xaml.dll!System.Xaml.Schema.XamlTypeInvoker.CreateInstance(object[] arguments) Unknown
System.Xaml.dll!MS.Internal.Xaml.Runtime.ClrObjectRuntime.CreateInstance(System.Xaml.XamlType xamlType, object[] args) Unknown
System.Xaml.dll!System.Xaml.XamlObjectWriter.Logic_CreateAndAssignToParentStart(MS.Internal.Xaml.Context.ObjectWriterContext ctx) Unknown
System.Xaml.dll!System.Xaml.XamlObjectWriter.WriteEndObject() Unknown
PresentationFramework.dll!System.Windows.FrameworkTemplate.LoadTemplateXaml(System.Xaml.XamlReader templateReader, System.Xaml.XamlObjectWriter currentWriter) Unknown
PresentationFramework.dll!System.Windows.FrameworkTemplate.LoadTemplateXaml(System.Xaml.XamlObjectWriter objectWriter) Unknown
PresentationFramework.dll!System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(System.Windows.DependencyObject container, System.Windows.Markup.IComponentConnector componentConnector, System.Windows.Markup.IStyleConnector styleConnector, System.Collections.Generic.List<System.Windows.DependencyObject> affectedChildren, System.Windows.UncommonField<System.Collections.Hashtable> templatedNonFeChildrenField) Unknown
PresentationFramework.dll!System.Windows.FrameworkTemplate.LoadContent(System.Windows.DependencyObject container, System.Collections.Generic.List<System.Windows.DependencyObject> affectedChildren) Unknown
PresentationFramework.dll!System.Windows.StyleHelper.ApplyTemplateContent(System.Windows.UncommonField<System.Collections.Specialized.HybridDictionary[]> dataField, System.Windows.DependencyObject container, System.Windows.FrameworkElementFactory templateRoot, int lastChildIndex, System.Collections.Specialized.HybridDictionary childIndexFromChildID, System.Windows.FrameworkTemplate frameworkTemplate) Unknown
PresentationFramework.dll!System.Windows.FrameworkTemplate.ApplyTemplateContent(System.Windows.UncommonField<System.Collections.Specialized.HybridDictionary[]> templateDataField, System.Windows.FrameworkElement container) Unknown
PresentationFramework.dll!System.Windows.FrameworkElement.ApplyTemplate() Unknown
PresentationFramework.dll!System.Windows.FrameworkElement.MeasureCore(System.Windows.Size availableSize) Unknown
PresentationCore.dll!System.Windows.UIElement.Measure(System.Windows.Size availableSize) Unknown
PresentationCore.dll!System.Windows.ContextLayoutManager.UpdateLayout() Unknown
PresentationCore.dll!System.Windows.ContextLayoutManager.UpdateLayoutCallback(object arg) Unknown
PresentationCore.dll!System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks() Unknown
It seems like the MeasureCore is tiggering the ApplyTemplate and so forth...
So the question is, how can I avoid that the template is recreated for every property call?
Related
I'm updating DataGrid through binding from different thread (Task).
I have zero problems with updating column values.
<DataGridTextColumn Header="NAME" MinWidth="100" Width="10*" Binding="{Binding name}" />
But some of my columns should also have different background color that is coming from database. And I can't update background property.
<DataGridTextColumn Header="" Width="10">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding color}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
I'm running into this issue:
Must create DependencySource on same Thread as the DependencyObject
Could you please advice what I do need to do?
P.S. Freeze() helps, but I suppose to think that it's not a way to do it.
So I solved it by making converter.
I have made new class
public sealed class ColorCodeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
...
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
...
}
}
Then connect it as resouce in the App.xaml
<local:ColorCodeConverter x:Key="ColorCodeConverter" />
And in the MainWindow.xaml I have following
<DataGridTextColumn Header="" Width="10">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding C7, Converter={StaticResource ColorCodeConverter}}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
So you just need to bind you string via ViewModel and that's all. No need to use Freeze(), etc.
Below binds the visibility of my checkbox to the converted bool. That works fine. How would i add a second condition? I only want to make the checkbox visible if the converted bool is true and another checkbox called Allowed is checked.
<CheckBox Grid.Row="3" Foreground="Black" Grid.ColumnSpan="2" x:Name="IsItComplete" IsThreeState="False"
BorderBrush="Black" VerticalContentAlignment="Center"
Checked="IsItComplete_Checked"
Style="{StaticResource CheckBoxStyle1}"
Visibility="{Binding Job.CanItBeComplete, Converter={StaticResource booleanToVisibilityConvertor},
Mode=OneWay,
Source={StaticResource Locator}}">
<CheckBox.Content>
<TextBlock Text="Is It Complete" Margin="0"/>
</CheckBox.Content>
</CheckBox>
If you are working under MVVM, one approach is to create a property in the ViewModel which will handle the logic and return a boolean indicating if the checkbox should be visible or not.
The second check box will have to be bound to a property as well, and be sure to do a TwoWay binding so that the property is updated when the checkbox is checked or not.
This should help: Bind two elements' Visibility to one property
This can be solved via two approaches i can think of over my head (ofcourse having single property in View model already suggested so i am not going to talk about it again)
Approach 1
Bind Visibility of checkBox with MultiValueConverter in place. Pass two bindings to it:
Actual bool property of class.
IsChecked DP of Allowed checkbox.
Converter will do AND operation on two values and will return accordingly.
<CheckBox>
<CheckBox.Visibility>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding Path="IsEnable"/>
<Binding ElementName="Allowed" Path="IsChecked"/>
</MultiBinding>
</CheckBox.Visibility>
</CheckBox>
MutliValueConverter code:
public class MyConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
return ((bool)values[0] && (bool)values[1])
? Visibility.Visible : Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
Approach 2
Use MultiDataTrigger in checkBox style like this:
<CheckBox>
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsEnable}" Value="True"/>
<Condition Binding="{Binding ElementName=Allowed,
Path=IsChecked}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
I have a Style on my data grid cell defined as follows:
<Style TargetType="{x:Type Editors:XamNumericEditor}" x:Key="MyVisibleStyle" BasedOn="{StaticResource InPointStyle}">
<Setter Property="Mask" Value="-nnnnnnnnnnn.nnnn"/>
<Setter Property="Format" Value="#,##0.000;-#,##0.000" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataItem.IsPermissioned}" Value="False" >
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
So depending on the value of DataItem.IsPermissioned I set the Visibility of the cell.
How can I change this so that if IsPermissioned=false then I display "NaN" in the cell?
Did you try creating a converter, which implements IValueConverter?
In the binding you would use it like
Binding="{Binding Path=Something, Converter={StaticResource yourConverter}}"
and your converter would be something like
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (validation on the value)
{ return "NaN" } }
You need to alternate the content property instead. So use the part below or add your own content depending on the cell pattern.
<Setter Property="Content" Value="NaN" />
I have a WPF style that I am applying to list box items. Currently, each item is bound to a KeyValuePair. I display the Key in a TextBlock inside the ListBoxItem:
<TextBlock Name="Label" Text="{Binding Key}" />
What I want to do is make the Style generic so that if the data is not a KeyValuePair, (maybe just a string), I can bind the data correctly to the TextBlock.
Is there a way to pass a parameter to a Style or DataTemplate or make the data binding generic?
My style:
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="Border" Padding="2" SnapsToDevicePixels="true" CornerRadius="4" BorderThickness="1">
<TextBlock Name="Label" Text="{Binding Key}" />
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter TargetName="Border" Property="Background" Value="{StaticResource SelectionGradient}" />
</MultiTrigger>
<ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Let me give you a quick example. Let's say your TextBox contains a number, and you want it to be with a red Background if the number is negative, or with a green background if >= 0
Here is the style you'd write:
<TextBlock Text="{Binding Key}" >
<TextBlock.Style>
<Style TargetType="TextBox">
<Setter Property="Background" Value="{Binding Key, Converter={StaticResource MyConverterResource}" />
</Style>
</TextBlock.Style>
</TextBlock>
Here is the converter you'd write:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double source = (double)value;
return source < 0 ? Brushes.Red : Brushes.Green;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// We don't care about this one here
throw new NotImplementedException();
}
}
And, to access the converter in your xaml, you just have to do the following, assuming your converter is in the namespace MyNamespace:
<Window xmlns:my="clr-namespace:MyNamespace">
<Window.Resources>
<my:MyConverter x:Key="MyConverterResource">
</Window.Resources?
<!-- Your XAML here -->
</Window>
(of course you can put this in any Resources, may it be a UserControl or whatever )
This will allow you to call your converter by writing {StaticResource MyConverterResource}
And here, you'd have a conditional style, the converter deciding which color to set as a background according to one parameter, in my case, the value itself (but it can be whatever you want)
I'm developing a WPF Page using .NET, MVVM, no code-behind, using PropertyChanged. In this Page, I have a DataGrid with a lot of columns.
Into the DB, one of the Columns, let's call it HIGHLIGHT, will have values S or N. If value = S, the entire line will be Bold, or ExtraBold. Case N = Normal.
I made this work using this code in XAML:
<Style x:Key="TextRowStyle" TargetType="{x:Type TextBlock}" >
<Style.Triggers>
<DataTrigger Binding="{Binding Slab.Highlight}" Value="S">
<Setter Property="FontWeight" Value="ExtraBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
But doing in this way, I'll have to put in EVERY COLUMN, this code to make it work (Notice the ElementStyle):
<DataGridTextColumn Header="Test" Binding="{Binding SlabSeq}" ElementStyle="{StaticResource TextRowStyle}"/>
What do I need:
Each table of my DB has several Columns, over 60, 70, and i'm searching for a way to make this more easy, like the StaticResource TextRowStyle i've made...
Another thing i've made, it was a Converter:
public class HighlightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (System.Convert.ToChar(value).Equals("S"))
return FontWeights.ExtraBold;
else
return FontWeights.Normal;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return FontWeights.ExtraBold;
}
}
I'd tried to make a converter inside those fields, using:
<Page.Resources>
<vm:HighlightConverter x:Key="HighlightConverter"/>
</Page.Resources>
and into the Grid:
<TextBlock FontWeight={Binding Slab.Highlight, Converter={StaticResource HighlightConverter}}"/>
Does anyone have any idea of how I can make this work?
Best regards,
Gustavo.
why you dont create a "local" style in your DataGrid.Resources. did i get it right that the entire row has to be bold (S) or normal (N)?
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}" >
<Setter Property="FontWeight" Value="Normal"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Slab.Highlight}" Value="S">
<Setter Property="FontWeight" Value="ExtraBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
with the code above all cells would be normal, but if a datarow has Slab.Highlight=S all cells would be bold. the code is not tested. maybe you have to add TextBlock.Fontweight or something like that.
What about applying the style implicitly by dropping the x:Key? That should make it apply everywhere in the grid when placed in the DataGrid.Resources.
This might work. Sorry, it's untested as I'm nowhere near an IDE.
<DataGrid>
<DataGrid.Resources>
<Style TargetType="DataGridTextColumn">
<Style.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontWeight" Value="{Binding Slab.Highlight, Converter={StaticResource HighlightConverter}}"/>
</Style>
</Style.Resources>
</Style>
</DataGrid.Resources>
</DataGrid>