Creating an AttachedProperty to hold positions for scrollbar markers - c#

I have created slightly customized vertical scrollbar for my DataGrid. In it I have added an ItemsControl to hold positions of the selected items. Here is a mockup so far with hard-coded markers.
Below is my customized vertical scrollbar template where the ItemsControl is placed with hard-coded marker values.
<ControlTemplate x:Key="VertScrollBar" TargetType="{x:Type ScrollBar}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="18" />
<RowDefinition Height="0.00001*" />
<RowDefinition MaxHeight="18" />
</Grid.RowDefinitions>
<Border Grid.RowSpan="3" CornerRadius="2" Background="#F0F0F0" />
<RepeatButton Grid.Row="0" Style="{StaticResource ScrollBarLineButton}" Height="18" Command="ScrollBar.LineUpCommand" Content="M 0 4 L 8 4 L 4 0 Z" />
<Track x:Name="PART_Track" Grid.Row="1" IsDirectionReversed="true">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageUpCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollBarThumb}" Margin="1,0,1,0">
<Thumb.BorderBrush>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource BorderLightColor}" Offset="0.0" />
<GradientStop Color="{DynamicResource BorderDarkColor}" Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Thumb.BorderBrush>
<Thumb.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource ControlLightColor}" Offset="0.0" />
<GradientStop Color="{DynamicResource ControlMediumColor}" Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Thumb.Background>
</Thumb>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageDownCommand" />
</Track.IncreaseRepeatButton>
</Track>
<!-- BEGIN -->
<ItemsControl Grid.Column="0" VerticalAlignment="Stretch" Name="ItemsSelected">
<sys:Double>30</sys:Double>
<sys:Double>70</sys:Double>
<sys:Double>120</sys:Double>
<sys:Double>170</sys:Double>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="SlateGray" Width="18" Height="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<!-- END -->
<RepeatButton Grid.Row="3" Style="{StaticResource ScrollBarLineButton}" Height="18" Command="ScrollBar.LineDownCommand" Content="M 0 0 L 4 4 L 8 0 Z" />
</Grid>
</ControlTemplate>
What I am trying to do next is create an AttachedProperty to hold marker positions and bind it back to the ItemsControl.
What I don't really understand is:
- What should this attached property Type be, an ObservableCollection of int's?
- As this is a guide to the total selected items in the DataGrid, do the positions of the markers need to be scaled somehow?
- I have an attached behavior that captures DataGrid.SelectionChanged, but what about if the main collection changes there doesn't seem to be an event for this?
[EDIT]
To bind directly to the DataGrids SelectedItems. (However there is a flicker in the top of the ItemsControl when something is selected)
- Remove or comment out the SelectionChanged behavior.
- Change the ItemSource to:
ItemsSource="{Binding ElementName=GenericDataGrid, Path=SelectedItems}"
- Change Multibinding to:
<MultiBinding Converter="{StaticResource MarkerPositionConverter}">
<Binding/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}" />
<Binding Path="ActualHeight" ElementName="ItemsSelected"/>
<Binding Path="Items.Count" ElementName="GenericDataGrid"/>
</MultiBinding>
- And lastly converter to:
public class MarkerPositionConverter: IMultiValueConverter
{
//Performs the index to translate conversion
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//calculated the transform values based on the following
object o = (object)values[0];
DataGrid dg = (DataGrid)values[1];
double itemIndex = dg.Items.IndexOf(o);
double trackHeight = (double)values[2];
int itemCount = (int)values[3];
double translateDelta = trackHeight / itemCount;
return itemIndex * translateDelta;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

I attempted to achieve your desired result
so for that I have made some changes, I've commented where I have made changes
add this converter reference to the resources
<helpers:MarkerPositionConverter x:Key="MarkerPositionConverter"/>
Items control xaml which is showing the markers
<!-- added Grid.Row="1", removed other attributes, removed the ItemsControlBeahviors, not much needed-->
<ItemsControl Grid.Row="1" Name="ItemsSelected"
ItemsSource="{Binding Source={x:Static helpers:MyClass.Instance}, Path=SelectedMarkers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--you can optionally bind height to scale accordingly if needed-->
<Rectangle Fill="#99708090" Width="18" Height="4">
<Rectangle.RenderTransform>
<!--added a translate transform-->
<TranslateTransform>
<TranslateTransform.Y>
<!--multi binded Y to the item and the actual height of MarkerItems control using the new MarkerPositionConverter-->
<MultiBinding Converter="{StaticResource MarkerPositionConverter}">
<Binding/>
<Binding Path="ActualHeight" ElementName="ItemsSelected"/>
</MultiBinding>
</TranslateTransform.Y>
</TranslateTransform>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
behavior.cs
public class DataGridBehaviors : Behavior<DataGrid>
{
...
void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MyClass.Instance.SelectedMarkers.Clear();
//updated item count
MyClass.Instance.ItemCount = this.AssociatedObject.Items.Count;
foreach (object o in this.AssociatedObject.SelectedItems)
MyClass.Instance.SelectedMarkers.Add(this.AssociatedObject.Items.IndexOf(o));
}
}
//removed ItemsControlBeahviors
public class MyClass : INotifyPropertyChanged
{
...
//added item count property
public int ItemCount { get; set; }
...
}
//added class to perform the index to translate conversion
public class MarkerPositionConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//calculated the transform values based on the following
double itemIndex = (double)values[0];
double trackHeight = (double)values[1];
double translateDelta = trackHeight / MyClass.Instance.ItemCount;
return itemIndex * translateDelta;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
now you may customize it as per your needs
Remove flicker
the flicker is due to the initial placement of the rectangle and before the binding gets all of its values so to avoid this initial intermittent flicker use this
<!--added fallback value to avoid intermittent value-->
<MultiBinding Converter="{StaticResource MarkerPositionConverter}" FallbackValue="-1000">
<Binding/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}" />
<Binding Path="ActualHeight" ElementName="ItemsSelected"/>
<Binding Path="Items.Count" ElementName="GenericDataGrid"/>
</MultiBinding>
so I placed the rectangle out of the view by pushing it back 1000 px when any of the binded property is busy in resolving the value or does not have any value.
and in items panel template (optional)
<ItemsPanelTemplate>
<!--added ClipToBounds to be extra safe-->
<Canvas ClipToBounds="True"/>
</ItemsPanelTemplate>
since the canvas by default does not clip its children, set ClipToBounds to true to be safe. this is necessary when the flicker is still visible somewhere in the UI even after using a huge fallback value.

Related

LiveCharts ColumnSeries not showing

I'm using LiveCharts in WPF to visualize the results of some analyses. The results of an analysis is added to a SeriesCollection and displayed in an CartesianChart. You can choose which type of series to use: LineSeries or ColumnSeries. The chosen type is then created and added to the SeriesCollection.
There's a custom mapper for selecting X and Y values from the ChartValues and a AxisFormatter for the X axis.
The charts are part of an Blacklight.Controls.Wpf.DragDockPanelHost. Each chart is an DragDockPanel with a style attached to it. The chart itself is a ContentControl with an TemplateSelector that returns the CartesianChart-XAML as a DataTemplate.
I've already tried to set the Fill or Stroke of the series or putting some ColumnSeries in there manually but that didn't help at all.
Filling of the SeriesCollection:
private SeriesCollection _Series;
public SeriesCollection Series
{
get { return _Series; }
set { SetProperty<SeriesCollection>(ref _Series, value); }
}
...
private void createDiagram()
{
if (this._Analysis!= null && this._Diagram != null)
{
this.Series.Clear();
foreach (KeyValuePair<state, Dictionary<DateTime, int>> kvp in this.Analysis.Execute())
{
Series series = Activator.CreateInstance(Diagram) as Series;
if (series != null)
{
series.Title = kvp.Key.name;
series.Values = new ChartValues<KeyValuePair<DateTime, int>>(kvp.Value);
this.Serien.Add(series);
}
}
}
}
Mapper and AxisFormatter:
CartesianMapper<KeyValuePair<DateTime, int>> mapper = Mappers.Xy<KeyValuePair<DateTime, int>>().X(kvp => ((DateTimeOffset)kvp.Key).ToUnixTimeSeconds()).Y(kvp => kvp.Value);
this.Series = new SeriesCollection(mapper);
this.XFormatter = value =>
{
return DateTimeOffset.FromUnixTimeSeconds((long)value).DateTime.ToString("dd.MM.yyyy HH:mm");
};
TemplateSelector:
public class DashboardElementTemplateSelector : DataTemplateSelector
{
public DataTemplate ListDashboardElementTemplate { get; set; }
public DataTemplate SingleValueDashboardElementTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is ListDashboardElementViewModel)
return this.ListDashboardElementTemplate;
else
return this.SingleValueDashboardElementTemplate;
}
}
XAML of DragDockPanelHost:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<DataTemplate x:Key="listElement">
<views:ListDashboardElementView/>
</DataTemplate>
<DataTemplate x:Key="singleValueElement">
<views:SingleValueDashboardElementView/>
</DataTemplate>
<tempselect:DashboardElementTemplateSelector x:Key="elementTempSelector"
ListDashboardElementTemplate="{StaticResource listElement}"
SingleValueDashboardElementTemplate="{StaticResource singleValueElement}"
/>
</ResourceDictionary>
<ResourceDictionary>
<conv:BooleanToVisibilityConverter x:Key="visCon"/>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<bl:DragDockPanelHost ItemsSource="{Binding Diagrams}" Grid.Row="1" Margin="20" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<bl:DragDockPanelHost.Style>
<Style TargetType="bl:DragDockPanelHost">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas ClipToBounds="True" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
</Canvas>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="bl:DragDockPanelHost">
<ItemsPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</bl:DragDockPanelHost.Style>
<bl:DragDockPanelHost.DefaultPanelStyle>
<Style TargetType="{x:Type bl:DragDockPanel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Margin="10">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Background="#00000000" Margin="-2" Padding="5" Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<WrapPanel>
<Image Width="20" x:Name="GripBarElement" Source="/Aisys.XStorage.Dashboard;component/Images/move.png" Grid.Column="0" Cursor="Hand" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Name}" Grid.Column="0" FontSize="16" FontWeight="Bold" Margin="10,0,0,0"/>
</WrapPanel>
<WrapPanel HorizontalAlignment="Right" Grid.Column="2">
<Button Command="{Binding ExecuteCommand}" CommandParameter="{Binding}" Margin="5,0,5,0">
<Button.Template>
<ControlTemplate>
<Image Source="/Aisys.XStorage.Dashboard;component/Images/Refresh.png"/>
</ControlTemplate>
</Button.Template>
</Button>
<Button Width="20" Command="{Binding DataContext.RemoveDiagramCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type bl:DragDockPanelHost}}}" CommandParameter="{Binding}">
<Button.Template>
<ControlTemplate>
<Image Source="/Aisys.XStorage.Dashboard;component/Images/Remove.png"/>
</ControlTemplate>
</Button.Template>
</Button>
<Button Command="{Binding DataContext.ShowPropertiesCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type bl:DragDockPanelHost}}}" CommandParameter="{Binding}" Margin="5,0,5,0">
<Button.Template>
<ControlTemplate>
<Image Source="/Aisys.XStorage.Dashboard;component/Images/Preferences.png"/>
</ControlTemplate>
</Button.Template>
</Button>
<ToggleButton x:Name="MaximizeToggleButton" VerticalAlignment="Top" HorizontalAlignment="Right" IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, Path=IsMaximized}" Margin="0,5,5,0" Width="25" Height="25" Cursor="Hand">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Image Source="/Aisys.XStorage.Dashboard;component/Images/Maximize.png" Margin="0,0,0,5"/>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</WrapPanel>
</Grid>
</Border>
<Separator VerticalAlignment="Bottom" Margin="0,0,0,0"/>
<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource elementTempSelector}" Grid.Row="1" Margin="10"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</bl:DragDockPanelHost.DefaultPanelStyle>
</bl:DragDockPanelHost>
XAML of chart:
<Grid>
<lvc:CartesianChart Series="{Binding Series}" LegendLocation="Right" Name="chart">
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Zeit" LabelFormatter="{Binding XFormatter}">
</lvc:Axis>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
</Grid>
If I'm choosing LineSeries, everything works fine. But when I'm using ColumnSeries nothing is shown. You can see, that the axis is redrawn and the separators move. The legend is also drawn, but there are no columns visible.
Any ideas why this is happening?
I had the same problem recently. Unfortunately, this doesn't seem to be documented anywhere but for some reason if you have too many data points for the size of the graph, then none of the columns will display. You can either try reducing the number of data points until it works (in my case 90 data points wouldn't display, but 30 would), or on ColumnSeries there is a property ColumnPadding that you can turn down to zero and see if that helps.

TemplateBinding in ControlTemplate in Style of CustomControl [duplicate]

I'm trying to create a button that behaves similarly to the "slide" button on the iPhone. I have an animation that adjusts the position and width of the button, but I want these values to be based on the text used in the control. Currently, they're hardcoded.
Here's my working XAML, so far:
<CheckBox x:Class="Smt.Controls.SlideCheckBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Smt.Controls"
xmlns:System.Windows="clr-namespace:System.Windows;assembly=PresentationCore"
Name="SliderCheckBox"
mc:Ignorable="d">
<CheckBox.Resources>
<System.Windows:Duration x:Key="AnimationTime">0:0:0.2</System.Windows:Duration>
<Storyboard x:Key="OnChecking">
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"
Duration="{StaticResource AnimationTime}"
To="40" />
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(Button.Width)"
Duration="{StaticResource AnimationTime}"
To="41" />
</Storyboard>
<Storyboard x:Key="OnUnchecking">
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"
Duration="{StaticResource AnimationTime}"
To="0" />
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(Button.Width)"
Duration="{StaticResource AnimationTime}"
To="40" />
</Storyboard>
<Style x:Key="SlideCheckBoxStyle"
TargetType="{x:Type local:SlideCheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SlideCheckBox}">
<Canvas>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
<Canvas>
<!--Background-->
<Rectangle Width="{Binding ElementName=ButtonText, Path=ActualWidth}"
Height="{Binding ElementName=ButtonText, Path=ActualHeight}"
Fill="LightBlue" />
</Canvas>
<Canvas>
<!--Button-->
<Button Width="{Binding ElementName=CheckedText, Path=ActualWidth}"
Height="{Binding ElementName=ButtonText, Path=ActualHeight}"
Name="CheckButton"
Command="{x:Static local:SlideCheckBox.SlideCheckBoxClicked}">
<Button.RenderTransform>
<TransformGroup>
<TranslateTransform />
</TransformGroup>
</Button.RenderTransform>
</Button>
</Canvas>
<Canvas>
<!--Text-->
<StackPanel Name="ButtonText"
Orientation="Horizontal"
IsHitTestVisible="False">
<Grid Name="CheckedText">
<Label Margin="7 0"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:SlideCheckBox}}, Path=CheckedText}" />
</Grid>
<Grid Name="UncheckedText"
HorizontalAlignment="Right">
<Label Margin="7 0"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:SlideCheckBox}}, Path=UncheckedText}" />
</Grid>
</StackPanel>
</Canvas>
</Canvas>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource OnChecking}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource OnUnchecking}" />
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Resources>
<CheckBox.CommandBindings>
<CommandBinding Command="{x:Static local:SlideCheckBox.SlideCheckBoxClicked}"
Executed="OnSlideCheckBoxClicked" />
</CheckBox.CommandBindings>
</CheckBox>
And the code behind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Smt.Controls
{
public partial class SlideCheckBox : CheckBox
{
public SlideCheckBox()
{
InitializeComponent();
Loaded += OnLoaded;
}
public static readonly DependencyProperty CheckedTextProperty = DependencyProperty.Register("CheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Checked Text"));
public string CheckedText
{
get { return (string)GetValue(CheckedTextProperty); }
set { SetValue(CheckedTextProperty, value); }
}
public static readonly DependencyProperty UncheckedTextProperty = DependencyProperty.Register("UncheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Unchecked Text"));
public string UncheckedText
{
get { return (string)GetValue(UncheckedTextProperty); }
set { SetValue(UncheckedTextProperty, value); }
}
public static readonly RoutedCommand SlideCheckBoxClicked = new RoutedCommand();
void OnLoaded(object sender, RoutedEventArgs e)
{
Style style = TryFindResource("SlideCheckBoxStyle") as Style;
if (!ReferenceEquals(style, null))
{
Style = style;
}
}
void OnSlideCheckBoxClicked(object sender, ExecutedRoutedEventArgs e)
{
IsChecked = !IsChecked;
}
}
}
The problem comes when I try to bind the "To" attribute in the DoubleAnimations to the actual width of the text, the same as I'm doing in the ControlTemplate. If I bind the values to an ActualWidth of an element in the ControlTemplate, the control comes up as a blank checkbox (my base class). However, I'm binding to the same ActualWidths in the ControlTemplate itself without any problems. Just seems to be the CheckBox.Resources that have a problem with it.
For instance, the following will break it:
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(Button.Width)"
Duration="{StaticResource AnimationTime}"
To="{Binding ElementName=CheckedText, Path=ActualWidth}" />
I don't know whether this is because it's trying to bind to a value that doesn't exist until a render pass is done, or if it's something else. Anyone have any experience with this sort of animation binding?
I've had similar situations in ControlTemplates where I've wanted to bind the "To" attribute to a value (rather than hard-coding it), and I finally found a solution.
Quick side note: If you dig around on the web you'll find examples of people being able to use data binding for the "From" or "To" properties. However, in those examples the Storyboards are not in a Style or ControlTemplate. If your Storyboard is in a Style or ControlTemplate, you'll have to use a different approach, such as this solution.
This solution gets around the freezable issue because it simply animates a double value from 0 to 1. It works with a clever use of the Tag property and a Multiply converter. You use a multibinding to bind to both a desired property and your "scale" (the Tag), which get multiplied together. Basically the idea is that your Tag value is what you animate, and its value acts like a "scale" (from 0 to 1) bringing your desired attribute value to "full scale" once you've animated the Tag to 1.
You can see this in action here. The crux of it is this:
<local:MultiplyConverter x:Key="multiplyConverter" />
<ControlTemplate x:Key="RevealExpanderTemp" TargetType="{x:Type Expander}">
<!-- (other stuff here...) -->
<ScrollViewer x:Name="ExpanderContentScrollView">
<!-- ** BEGIN IMPORTANT PART #1 ... -->
<ScrollViewer.Tag>
<sys:Double>0.0</sys:Double>
</ScrollViewer.Tag>
<ScrollViewer.Height>
<MultiBinding Converter="{StaticResource multiplyConverter}">
<Binding Path="ActualHeight" ElementName="ExpanderContent"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</ScrollViewer.Height>
<!-- ...end important part #1. -->
<ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>
</ScrollViewer>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<!-- ** BEGIN IMPORTANT PART #2 (make TargetProperty 'Tag') ... -->
<DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"
Storyboard.TargetProperty="Tag"
To="1"
Duration="0:0:0.4"/>
<!-- ...end important part #2 -->
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
With this value converter:
public class MultiplyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double result = 1.0;
for (int i = 0; i < values.Length; i++)
{
if (values[i] is double)
result *= (double)values[i];
}
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new Exception("Not implemented");
}
}
As far as I know, you can't bind the animation to/from because the animation has to be freezable.
I like #Jason Frank's solution. However it is even easier and less error-prone if you don't use the Tag, but instead e.g. the Width property of an empty dummy Border element. It is a native double property, so no need for <sys:Double> syntax and you can name the Border just like you would do with a variable like so:
<!-- THIS IS JUST USED FOR SLIDING ANIMATION MATH -->
<!-- animated Border.Width From 0 to 1 -->
<Border x:Name="Var_Animation_0to1" Width="0"/>
<!-- animated Border.Width From 0 to (TotalWidth-SliderWidth) -->
<Border x:Name="Var_Slide_Length">
<Border.Width>
<MultiBinding Converter="{mvvm:MathConverter}" ConverterParameter="a * (b-c)">
<Binding ElementName="Var_Animation_0to1" Path="Width"/>
<Binding ElementName="BackBorder" Path="ActualWidth"/>
<Binding ElementName="Slider" Path="ActualWidth"/>
</MultiBinding>
</Border.Width>
</Border>
That makes the bindings much more readable.
The Animation is always 0..1, as Jason pointed out:
<BeginStoryboard Name="checkedSB">
<Storyboard Storyboard.TargetProperty="Width" Storyboard.TargetName="Var_Animation_0to1">
<DoubleAnimation To="1" Duration="00:00:00.2"/>
</Storyboard>
</BeginStoryboard>
Then bind whatever you want to animate to the Width of the dummy border. This way you can even chain converters to each other like so:
<Border x:Name="Slider" HorizontalAlignment="Left"
Margin="{Binding ElementName=Var_Slide_Length, Path=Width, Converter={StaticResource DoubleToThickness}, ConverterParameter=x 0 0 0}"/>
Combined with the MathConverter you can do almost anything in styles:
https://www.codeproject.com/Articles/239251/MathConverter-How-to-Do-Math-in-XAML
I implemented this exact thing.
<UserControl x:Class="YOURNAMESPACE.UserControls.SliderControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:YOURNAMESPACE.UserControls"
xmlns:converter="clr-namespace:YOURNAMESPACE.ValueConverters"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
SizeChanged="UserControl_SizeChanged">
<UserControl.Resources>
<converter:MathConverter x:Key="mathConverter" />
<LinearGradientBrush x:Key="CheckedBlue" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#e4f5fc" Offset="0" />
<GradientStop Color="#e4f5fc" Offset="0.1" />
<GradientStop Color="#e4f5fc" Offset="0.1" />
<GradientStop Color="#9fd8ef" Offset="0.5" />
<GradientStop Color="#9fd8ef" Offset="0.5" />
<GradientStop Color="#bfe8f9" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="CheckedOrange" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFCA6A13" Offset="0" />
<GradientStop Color="#FFF67D0C" Offset="0.1" />
<GradientStop Color="#FFFE7F0C" Offset="0.1" />
<GradientStop Color="#FFFA8E12" Offset="0.5" />
<GradientStop Color="#FFFF981D" Offset="0.5" />
<GradientStop Color="#FFFCBC5A" Offset="1" />
</LinearGradientBrush>
<SolidColorBrush x:Key="CheckedOrangeBorder" Color="#FF8E4A1B" />
<SolidColorBrush x:Key="CheckedBlueBorder" Color="#FF143874" />
<Style x:Key="CheckBoxSlider" TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" />
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}" >
<DockPanel x:Name="dockPanel"
Width="{TemplateBinding ActualWidth}"
Height="{TemplateBinding Height}" >
<DockPanel.Resources>
<Storyboard x:Key="ShowRightStoryboard">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="ShowLeftStoryboard" >
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="slider"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"
>
<SplineDoubleKeyFrame x:Name="RightHalfKeyFrame" KeyTime="00:00:00.1000000" Value="300" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</DockPanel.Resources>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True"
VerticalAlignment="Center" />
<Grid>
<Border x:Name="BackgroundBorder" BorderBrush="#FF939393" BorderThickness="1" CornerRadius="3"
Width="{TemplateBinding ActualWidth}"
Height="{TemplateBinding Height}" >
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFB5B5B5" Offset="0" />
<GradientStop Color="#FFDEDEDE" Offset="0.1" />
<GradientStop Color="#FFEEEEEE" Offset="0.5" />
<GradientStop Color="#FFFAFAFA" Offset="0.5" />
<GradientStop Color="#FFFEFEFE" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock x:Name="LeftTextBlock" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=LeftText, Mode=TwoWay}" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock x:Name="RightTextBlock" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=RightText, Mode=TwoWay}" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Border>
<Border x:Name="slider"
BorderBrush="#FF939393"
HorizontalAlignment="Left"
Width="{TemplateBinding ActualWidth, Converter={StaticResource mathConverter}, ConverterParameter=/2}"
Height="{TemplateBinding Height}"
BorderThickness="1"
CornerRadius="3"
RenderTransformOrigin="0.5,0.5" Margin="0"
>
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1" />
<SkewTransform AngleX="0" AngleY="0" />
<RotateTransform Angle="0" />
<TranslateTransform X="{TemplateBinding ActualWidth, Converter={StaticResource mathConverter}, ConverterParameter=/2}" Y="0" />
</TransformGroup>
</Border.RenderTransform>
<Border.Background>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF0F0F0" Offset="0" />
<GradientStop Color="#FFCDCDCD" Offset="0.1" />
<GradientStop Color="#FFFBFBFB" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<DockPanel Background="Transparent" LastChildFill="False">
<Viewbox x:Name="SlideRight" Stretch="Uniform" Width="28" Height="28" DockPanel.Dock="Right" Margin="0,0,50,0" >
<Path Stretch="Fill" Fill="{DynamicResource TextBrush}">
<Path.Data>
<PathGeometry Figures="m 27.773437 48.874779 -8.818359 9.902343 -4.833984 0 8.847656 -9.902343 -8.847656 -10.019532 4.833984 0 z m -11.396484 0 -8.7597655 9.902343 -4.9804687 0 9.0234372 -9.902343 -9.0234372 -10.019532 4.9804687 0 z" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
<Viewbox x:Name="SlideLeft" Stretch="Uniform" Width="28" Height="28" DockPanel.Dock="Left" Margin="50,0,0,0" >
<Path Stretch="Fill" Fill="{DynamicResource TextBrush}">
<Path.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="-1"/>
</TransformGroup>
</Path.LayoutTransform>
<Path.Data>
<PathGeometry Figures="m 27.773437 48.874779 -8.818359 9.902343 -4.833984 0 8.847656 -9.902343 -8.847656 -10.019532 4.833984 0 z m -11.396484 0 -8.7597655 9.902343 -4.9804687 0 9.0234372 -9.902343 -9.0234372 -10.019532 4.9804687 0 z" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
</DockPanel>
</Border>
</Grid>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="BackgroundBorder" Property="Background" Value="{StaticResource CheckedOrange}" />
<Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="{StaticResource CheckedOrangeBorder}" />
<Setter TargetName="SlideRight" Property="Visibility" Value="Collapsed" />
<Setter TargetName="SlideLeft" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="BackgroundBorder" Property="Background" Value="{StaticResource CheckedBlue}" />
<Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="{StaticResource CheckedBlueBorder}" />
<Setter TargetName="SlideRight" Property="Visibility" Value="Visible" />
<Setter TargetName="SlideLeft" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBox"
Style="{StaticResource CheckBoxSlider}"
HorizontalAlignment="Stretch"
DockPanel.Dock="Top"
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=Height, Mode=TwoWay}"
IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=IsLeftVisible, Mode=TwoWay}"
Checked="CheckBox_Checked"
Unchecked="CheckBox_Unchecked"
/>
</Grid>
</UserControl>
code behind.
namespace YOURNAMESPACE.UserControls
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
/// <summary>
/// Interaction logic for SliderControl.xaml
/// </summary>
public partial class SliderControl : UserControl
{
public static readonly DependencyProperty IsLeftVisibleProperty =
DependencyProperty.RegisterAttached(
"IsLeftVisible",
typeof(bool),
typeof(SliderControl),
new UIPropertyMetadata(true, IsLeftVisibleChanged));
public static readonly DependencyProperty LeftTextProperty =
DependencyProperty.RegisterAttached(
"LeftText",
typeof(string),
typeof(SliderControl),
new UIPropertyMetadata(null, LeftTextChanged));
public static readonly DependencyProperty RightTextProperty =
DependencyProperty.RegisterAttached(
"RightText",
typeof(string),
typeof(SliderControl),
new UIPropertyMetadata(null, RightTextChanged));
/// <summary>
/// Initializes a new instance of the <see cref="SliderControl"/> class.
/// </summary>
public SliderControl()
{
this.InitializeComponent();
}
public string LeftText { get; set; }
public string RightText { get; set; }
[AttachedPropertyBrowsableForType(typeof(SliderControl))]
public static bool GetIsLeftVisible(SliderControl sliderControl)
{
return (bool)sliderControl.GetValue(IsLeftVisibleProperty);
}
[AttachedPropertyBrowsableForType(typeof(SliderControl))]
public static string GetLeftText(SliderControl sliderControl)
{
return (string)sliderControl.GetValue(LeftTextProperty);
}
public static void SetIsLeftVisible(SliderControl sliderControl, bool value)
{
sliderControl.SetValue(IsLeftVisibleProperty, value);
}
public static void IsLeftVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SliderControl slider = d as SliderControl;
if ((bool)e.NewValue == true)
{
slider.RunAnimation("ShowLeftStoryboard");
}
else
{
slider.RunAnimation("ShowRightStoryboard");
}
}
[AttachedPropertyBrowsableForType(typeof(SliderControl))]
public static string GetRightText(SliderControl sliderControl)
{
return (string)sliderControl.GetValue(RightTextProperty);
}
public static void SetLeftText(SliderControl sliderControl, string value)
{
sliderControl.SetValue(LeftTextProperty, value);
}
public static void SetRightText(SliderControl sliderControl, string value)
{
sliderControl.SetValue(RightTextProperty, value);
}
private static void LeftTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SliderControl slider = d as SliderControl;
slider.LeftText = e.NewValue as string;
}
private static void RightTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SliderControl slider = d as SliderControl;
slider.RightText = e.NewValue as string;
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.checkBox.Width = e.NewSize.Width;
CheckBox cb = this.checkBox;
var controlTemplate = cb.Template;
DockPanel dockPanel = controlTemplate.FindName("dockPanel", cb) as DockPanel;
Storyboard story = dockPanel.Resources["ShowLeftStoryboard"] as Storyboard;
DoubleAnimationUsingKeyFrames dk = story.Children[0] as DoubleAnimationUsingKeyFrames;
SplineDoubleKeyFrame sk = dk.KeyFrames[0] as SplineDoubleKeyFrame;
// must manipulate this in code behind, binding does not work,
// also it cannot be inside of a control template
// because storyboards in control templates become frozen and cannot be modified
sk.Value = cb.Width / 2;
if (cb.IsChecked == true)
{
story.Begin(cb, cb.Template);
}
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
this.RunAnimation("ShowLeftStoryboard");
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
this.RunAnimation("ShowRightStoryboard");
}
private void RunAnimation(string storyboard)
{
CheckBox cb = this.checkBox;
var controlTemplate = cb.Template;
DockPanel dockPanel = controlTemplate.FindName("dockPanel", cb) as DockPanel;
if (dockPanel != null)
{
Storyboard story = dockPanel.Resources[storyboard] as Storyboard;
DoubleAnimationUsingKeyFrames dk = story.Children[0] as DoubleAnimationUsingKeyFrames;
SplineDoubleKeyFrame sk = dk.KeyFrames[0] as SplineDoubleKeyFrame;
story.Begin(cb, cb.Template);
}
}
}
}
IValueConverter
namespace YOURNAMESPACE.ValueConverters
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
/// <summary>
/// Does basic math operations, eg. value = 60 parameter = "*2 + 1", result = 121
/// </summary>
/// <seealso cref="System.Windows.Data.IValueConverter" />
[ValueConversion(typeof(double), typeof(double))]
public class MathConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double d = (double)value;
string mathExpression = d.ToString() + parameter;
if (mathExpression.Contains("^"))
{
throw new Exception("Doesn't handle powers or square roots");
}
DataTable table = new DataTable();
table.Columns.Add("expression", typeof(string), mathExpression);
DataRow row = table.NewRow();
table.Rows.Add(row);
double ret = double.Parse((string)row["expression"]);
if (ret <= 0)
{
return 1d;
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// not implemented
return null;
}
}
}
example usage:
<chart:SliderControl x:Name="sliderControl"
LeftText="{Binding Path=LeftTextProperty}"
RightText="{Binding Path=RightTextProperty}"
Grid.Row="0"
IsLeftVisible="{Binding Path=IsLeftVisible, Mode=TwoWay}"
Height="35" />

getting textBlock from list view dynamically

I have a ListView which contain 2 textBlocks as a listview Item
I want to color the textBlocks dynamically
how can I get do the textblock?
will appreciate an example.
Thanks you for your support
attaching My XAML:
<ListView x:Name="LV" ItemsSource= "{Binding Lggv}" SelectionChanged="dataGridData_SelectionChanged" ItemContainerStyle="{StaticResource ListViewItemStyle}" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel >
<Border BorderThickness="1" BorderBrush="Black">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock x:Name="tbHeader" Text="{Binding Info }" AllowDrop="True" FontWeight="Bold" Grid.Column="2" >
<TextBlock.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFCEE6C6" Offset="0.008"/>
<GradientStop Color="#FF9ECF8C" Offset="0.987"/>
</LinearGradientBrush>
</TextBlock.Background>
</TextBlock>
</Grid>
</Border>
<Grid x:Name="GridData" >
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFC5DDFF" Offset="0"/>
<GradientStop Color="#FFA8C8F7" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderThickness="2" BorderBrush="Black">
<TextBlock Text="{Binding DateTime}" ></TextBlock>
</Border>
<Border Grid.Column="1" BorderThickness="2" BorderBrush="Black">
<TextBlock Text="{Binding ComPort}"></TextBlock>
</Border>
<Border Grid.Column="2" BorderThickness="2" BorderBrush="Black">
<TextBlock Text="{Binding Data}" ></TextBlock>
</Border>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The best way to do this is with a IValueConverter
as this allows you to keep your view and viewModel seperate, though if you don't care about that #sasanaf answer is perfertly correct
here is an example of a simple Brush based converter
public class PriorityConverter : IValueConverter
{
public Brush HighBrush { get; set; }
public Brush LowBrush { get; set; }
public Brush MediumBrush { get; set; }
public Brush DefaultBrush { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var priority = value as Priority?;
if (priority.HasValue)
{
switch (priority.Value)
{
case Priority.High:
return HighBrush;
case Priority.Medium:
return MediumBrush;
case Priority.Low:
return LowBrush;
default:
return DefaultBrush;
}
}
else
throw new InvalidCastException($"{value} is not a Priority");
}
You would then add the converter as a resource to your App,
<local:PriorityConverter x:Key="PriorityConverter" >
<local:PriorityConverter.DefaultBrush>
<SolidColorBrush Color="{DynamicResource {x:Static SystemColors.ControlColorKey}}"/>
</local:PriorityConverter.DefaultBrush>
<local:PriorityConverter.HighBrush>
<LinearGradientBrush>
<GradientStop Color="Red" Offset="0.5"/>
<GradientStop Color="Yellow" />
</LinearGradientBrush>
</local:PriorityConverter.HighBrush>
<local:PriorityConverter.MediumBrush>
<SolidColorBrush Color="Blue"/>
</local:PriorityConverter.MediumBrush>
<local:PriorityConverter.LowBrush>
<SolidColorBrush Color="Green"/>
</local:PriorityConverter.LowBrush>
</local:PriorityConverter>
and finally use it on your binding
<TextBlock Background="{Binding Priority, Converter={StaticResource PriorityConverter}}" />
If you are having a MVVM approach, i would set the colors in the ViewModel and bind them to the properties.
Edit:
Textblock like this:
<TextBlock DataContext="ViewModel"
Background="{Binding SpecificColor}">
</TextBlock>
And in ViewModel you can specifiy at initialization or at any specific event wich color you would like.
For Example :
private Brush m_SpecificColor;
public Brush SpecificColor
{
get { return m_SpecificColor; }
set
{
m_SpecificColor = value;
OnPropertyChanged("SpecificColor");
}
}
private void SetColor()
{
SpecificColor = (Brush)new BrushConverter().ConvertFromString("Green");
}
I guess you want to change the colour dynamically based on some property of the object (the class with the "Info", "DateTime", "ComPort" and "Data" properties) in your Lggv source collection.
You could then use a style with a DataTrigger that binds to a property of this class, i.e. the the type T (again this is the one with the "Info", "DateTime", "ComPort" and "Data" properties) of the IEnumerable<T> (Lggv) source collection that you bind to:
<TextBlock x:Name="tbHeader" Text="{Binding Info}" AllowDrop="True" FontWeight="Bold" Grid.Column="2">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Background">
<Setter.Value>
<!-- this is the default background-->
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFCEE6C6" Offset="0.008"/>
<GradientStop Color="#FF9ECF8C" Offset="0.987"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Style.Triggers>
<!-- This trigger changes the background to green when the "Info" property of your data object returns "Some info..." -->
<DataTrigger Binding="{Binding Info}" Value="Some info...">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush>Green</SolidColorBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
You can use IValueConverter to convert property to background color. Below is an example.
class BackgroundColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
LinearGradientBrush myLinearGradientBrush = new LinearGradientBrush();
if (value != null )
{
string strValue = value.ToString();
myLinearGradientBrush.StartPoint = new System.Windows.Point(0, 0);
myLinearGradientBrush.EndPoint = new System.Windows.Point(1, 1);
switch (strValue)
{
case "Match1":
myLinearGradientBrush.GradientStops.Add(new GradientStop(Colors.Yellow, 0.0));
myLinearGradientBrush.GradientStops.Add(new GradientStop(Colors.Red, 0.25));
break;
case "Match2":
myLinearGradientBrush.GradientStops.Add(new GradientStop(Colors.Blue , 0.0));
myLinearGradientBrush.GradientStops.Add(new GradientStop(Colors.BlueViolet, 0.25));
break;
default:
myLinearGradientBrush.GradientStops.Add(new GradientStop(Colors.Green , 0.0));
myLinearGradientBrush.GradientStops.Add(new GradientStop(Colors.GreenYellow, 0.25));
break;
}
}
return myLinearGradientBrush;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Declare value convert in resource as below.
<Window.Resources>
<local:BackgroundColorConverter x:Key="BackgroundColorConverter"/>
</Window.Resources>
Below is the coder of Xaml.
<TextBlock x:Name="tbHeader" Text="{Binding Info }" Background="{Binding Path=property, Converter={StaticResource BackgroundColorConverter}}" AllowDrop="True" FontWeight="Bold" Grid.Column="2"/>
another approach if you want to use resource. You can define color in windows or user control resource. After this you will have to use IMultiValueConverter. Pass you property and windows or user control to MultiValueConverter. You need to use relative source to get windows or usercontrol in binding. Then you can return the resource as per the matching condition.
Personally I'd do this with a DataTrigger:
<Style x:Key="TextBlockStyle" TargetType="TextBlock">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFCEE6C6" Offset="0.008"/>
<GradientStop Color="#FF9ECF8C" Offset="0.987"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding YourProperty}" Value="YourPropertyValue">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Red" Offset="0.008"/>
<GradientStop Color="Orange" Offset="0.987"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
You don't then have to have a class defined to do what is a relatively simple task.

How do I bind a LinearGradientBrush in the GanttControl?

I have a GanttControl with a TaskItemTemplate containing a Grid and a Rectangle which is responsible for the appearence of the tasks. The Rectangle has the properties Fill and Stroke. These properties are being set from StaticResources where the color of the tasks are defined. I try to bind the LinearGradientBrush to the property Color of my model.
There is my Xaml code:
<LinearGradientBrush x:Key="ParentBarFill" StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="White" Offset="0.1" />
<GradientStop Color="Gray" Offset="0.2" />
<GradientStop Color="Black" Offset="0.6" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
** />
<SolidColorBrush x:Key="TaskItemBarStroke" Color="{Binding Color}"/> **<!--There is the Problem too-->**
<!-- Task look and feel styles -->
<!--Transparent-->
<Style TargetType="Thumb" x:Name="TransparentThumb" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Grid>
<Border CornerRadius="2" Background="Transparent" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<flexyGantt:FlexyGantt x:Name="fxgantt" Grid.Row="1" Grid.Column="0" ItemTemplate="{StaticResource TreeTemplate}"
TaskListBinding="{Binding EngineersInterval}" TaskStartTimeBinding="{Binding StartInteval, Mode=TwoWay}"
TaskEndTimeBinding="{Binding FinishInterval, Mode=TwoWay}" ParentTaskStartTimeBinding="{Binding OverallStartTime}"
ParentTaskEndTimeBinding="{Binding OverallEndTime}"
TreeHeaderContent="Teams" CustomSourceListViewTemplate="{StaticResource flexyGanttTableTemplate}"
OverlappedTasksRenderingOptimization="ShrinkHeight" RowHeight="25" TaskTimeChanged="Fxgantt_OnTaskTimeChanged"
>
<flexyGantt:FlexyGantt.TaskItemTemplate>
<DataTemplate>
<Grid ToolTipService.ToolTip="{Binding ToolTipText}">
<Rectangle x:Name="taskBar" HorizontalAlignment="Stretch"
Fill="{StaticResource TaskItemBarFill}" Stroke="{StaticResource TaskItemBarStroke}"
StrokeThickness="1" RadiusX="4" RadiusY="4"/>
<!--Include a thumb with name "dragThumb" to enable drag-moving the task functionality.
ZIndex=2 ensures this is drawn above the Rectangle-->
<Thumb x:Name="dragThumb" Canvas.ZIndex="2" Style="{StaticResource TransparentThumb}" />
<!--Include a thumb with name "resizeThumb" to enable resizing the task functionality
ZIndex=3 ensures this is drawn above the dragThumb-->
<Thumb x:Name="resizeThumb" Canvas.ZIndex="4" HorizontalAlignment="Right" Cursor="SizeWE"
Style="{StaticResource TransparentThumb}" Width="3" />
<!--Include a thumb with name "resizeStartThumb" to enable resizing to the left (changing starttime) functionality
ZIndex=3 ensures this is drawn above the dragThumb-->
<Thumb x:Name="resizeStartThumb" Canvas.ZIndex="3" HorizontalAlignment="Left" Cursor="SizeWE"
Style="{StaticResource TransparentThumb}" Width="3" Height="14"/>
</Grid>
</DataTemplate>
</flexyGantt:FlexyGantt.TaskItemTemplate>
</flexyGantt:FlexyGantt>
How can I bind this? Also me Model :
public class GanttIntervalModel : INotifyPropertyChanged
{
public Color Color
{
get { return _color; }
set
{
if (Equals(value, _color)) return;
_color = value;
OnPropertyChanged(nameof(Color));
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

WPF Listbox refresh

Problem
I have a Listbox control and when a user clicks a button they are able to add items to the control.
The issue I am having is that the Listbox control itself is not refreshing and displaying the new values that the user has added. I have debugged the code and the datasource is being updated with the new values.
Anyone have any ideas?
Code
The C#:
ObservableCollection<ITimeLineDataItem> listboxData = new ObservableCollection<ITimeLineDataItem>();
public ObservableCollection<ITimeLineDataItem> ListBoxData
{
get
{
return listboxData;
}
}
public Live()
{
InitializeComponent();
this.DataContext = this;
}
public void RefreshListbox()
{
ListSrc.ItemsSource = null;
//THE CODE GOES HERE WHERE I UPDATE THE DATASOURCE.
ListSrc.ItemsSource = ListBoxData;
}
XAML:
<Border BorderBrush="#d6786a" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="2" Grid.Column="15" Grid.Row="5" Grid.ColumnSpan="3">
<ListBox x:Name="ListSrc" Background="#ececec" ItemsSource="{Binding Path=ListBoxData}" dd:DragDrop.IsDragSource="True" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" BorderBrush="Transparent" BorderThickness="0">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="10" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="4" Margin="15"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
</ListBox.Resources>
</ListBox>
</Border>
You don't need to refresh. If the new item is added correctly to the source it should be visible on the list. However, can you update your post with the button click event code?

Categories

Resources