Dynamic Axis in WPF Toolkit LineSeries Chart - c#

I am using WPF Toolkit for charting. I am using a LineSeries for displaying the data change per second. Currently, I am able to update the graph as new points are added. But the X-Axis scale is fixed from 0 to 60 automatically. What I want is, after the first cycle, instead of the data plot showing from the starting of the axis, I want the X-Axis to shift by one division, like it is in an ECG display.

I managed to find the answer on my own. Please give suggestions fro improving the answer.
namespace WpfChartExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<ChartData> chartData;
ChartData objChartData;
Thread MyThread;
public MainWindow()
{
InitializeComponent();
chartData = new ObservableCollection<ChartData>();
DateTime dtnow = DateTime.Now;
objChartData = new ChartData() { Name = dtnow, Value = 0.0 };
chartData.Add(objChartData);
chartData.Add(new ChartData() { Name = (dtnow + TimeSpan.FromSeconds(2)), Value = new Random().NextDouble() * 100 });
chartData.Add(new ChartData() { Name = (dtnow + TimeSpan.FromSeconds(4)), Value = new Random().NextDouble() * 100 });
xAxis.Minimum = chartData[0].Name;
simChart.DataContext = chartData;
MyThread = new Thread(new ThreadStart(StartChartDataSimulation));
}
public void StartChartDataSimulation()
{
int i = 0;
while (true)
{
Dispatcher.Invoke(new Action(() =>
{
var data = new ChartData() { Name = DateTime.Now, Value = new Random().NextDouble() * 100 };
chartData.Add(data);
if (chartData.Count % 40 == 0 && i == 0)
{
xAxis.Minimum = chartData[i + 1].Name;
i++;
}
if (i >= 1)
{
xAxis.Minimum = chartData[i + 1].Name;
i++;
}
}));
Thread.Sleep(1000);
}
}
private void btnStartStop_Click(object sender, RoutedEventArgs e)
{
if ((string)btnStartStop.Content == "Start Simulation")
{
if (MyThread.ThreadState == ThreadState.Unstarted)
{
MyThread.Start();
}
else if (MyThread.ThreadState == ThreadState.Suspended)
{
MyThread.Resume();
}
btnStartStop.Content = "Stop Simulation";
}
else
{
MyThread.Suspend();
btnStartStop.Content = "Start Simulation";
}
}
private void Window_Closing(object sender, CancelEventArgs e)
{
if (MyThread.ThreadState == ThreadState.Running ||MyThread.ThreadState == ThreadState.WaitSleepJoin)
{
MyThread.Suspend();
}
Application.Current.Shutdown();
}
}
public class ChartData : INotifyPropertyChanged
{
DateTime _Name;
double _Value;
#region properties
public DateTime Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public double Value
{
get
{
return _Value;
}
set
{
_Value = value;
OnPropertyChanged("Value");
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
<Window x:Class="WpfChartExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:chrt="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
Title="MainWindow" Height="350" Width="525" Closing="Window_Closing">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<chrt:Chart x:Name="simChart" Title="Simulation" Background="Beige">
<chrt:LineSeries IndependentValueBinding="{Binding Name}"
DependentValueBinding="{Binding Value}"
ItemsSource="{Binding}"
Background="DarkGray">
<chrt:LineSeries.DataPointStyle>
<Style TargetType="{x:Type chrt:LineDataPoint}">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="Width" Value="5" />
<Setter Property="Height" Value="5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="chrt:LineDataPoint">
<Grid x:Name="Root" Opacity="1">
<ToolTipService.ToolTip>
<StackPanel Margin="2,2,2,2">
<ContentControl Content="{TemplateBinding IndependentValue}"
ContentStringFormat="X-Value: {0:HH:mm:ss}"/>
<ContentControl Content="{TemplateBinding DependentValue}"
ContentStringFormat="Y-Value: {0:###.###}"/>
</StackPanel>
</ToolTipService.ToolTip>
<Ellipse StrokeThickness="{TemplateBinding BorderThickness}"
Stroke="{TemplateBinding BorderBrush}"
Fill="{TemplateBinding Background}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</chrt:LineSeries.DataPointStyle>
<chrt:LineSeries.IndependentAxis>
<chrt:DateTimeAxis Name="xAxis" ShowGridLines="True" Orientation="X">
<chrt:DateTimeAxis.AxisLabelStyle>
<Style TargetType="chrt:DateTimeAxisLabel">
<Setter Property="StringFormat" Value="{}{0:mm:ss}" />
</Style>
</chrt:DateTimeAxis.AxisLabelStyle>
</chrt:DateTimeAxis>
</chrt:LineSeries.IndependentAxis>
<chrt:LineSeries.DependentRangeAxis>
<chrt:LinearAxis Orientation="Y" ShowGridLines="True" Minimum="-50" Maximum="50"></chrt:LinearAxis>
</chrt:LineSeries.DependentRangeAxis>
<chrt:LineSeries.Title>
<TextBlock TextAlignment="Center">Time<LineBreak/>vs.<LineBreak/>Random<LineBreak/>Data</TextBlock>
</chrt:LineSeries.Title>
</chrt:LineSeries>
</chrt:Chart>
<Button Name="btnStartStop" Width="Auto" Height="30" Grid.Row="1" HorizontalAlignment="Right" Margin="10" Click="btnStartStop_Click">Start Simulation</Button>
</Grid>

Related

How do I change a line in a datagrid that I marked (MVVM Structure)

I can add texts to the DataGrid and by clicking the button (edit) I can drag the data from the DataGrid back into the text boxes, but my problem is if I change the data after dragging it over, it is not updated in the Datagrid.
<Canvas x:Name="CV_Projekte" Grid.Column="1" Background="White" Visibility="Visible">
<Label Content="Projektnumber:" Canvas.Left="44" Canvas.Top="84" FontSize="14" FontWeight="Bold"/>
<Label Content="Name:" Canvas.Left="43" Canvas.Top="113" FontSize="14" FontWeight="Bold" />
<Label Content="Unterposition:" Canvas.Left="44" Canvas.Top="146" FontSize="14" FontWeight="Bold" />
<Label Content="Describe:" Canvas.Left="44" Canvas.Top="182" FontSize="14" FontWeight="Bold" />
<TextBox Canvas.Left="183" Canvas.Top="89" TextWrapping="Wrap" Width="120" Text="{Binding ProjectNumber}" />
<TextBox Canvas.Left="44" Canvas.Top="216" TextWrapping="Wrap" Width="273" Height="155"/>
<TextBox Canvas.Left="183" Canvas.Top="151" TextWrapping="Wrap" Width="120"/>
<TextBox Canvas.Left="183" Canvas.Top="118" TextWrapping="Wrap" Width="120" Text="{Binding ProjectName}"/>
<Button Content="Add" Canvas.Left="383" Canvas.Top="167" Width="75" Command="{Binding StartCommand}" Opacity="0.2"/>
<Button Content="Edit" Canvas.Left="383" Canvas.Top="201" Width="75" Opacity="0.2" Command="{Binding UpdateCommand}" />
<DataGrid Height="575" Canvas.Left="616" Width="303" ItemsSource="{Binding CareList}" SelectedItem="{Binding SelectedCare}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding Projektnummer}"/>
<DataGridTextColumn Header="Projektname" Width="184" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Delete"/>
</DataGrid.Columns>
</DataGrid>
</Canvas>
This is the CareViewModel.cs
public class CareViewModel :INotifyPropertyChanged
{
private int _ProtjectNumber;
private string _ProjectName;
private Visibility _TrackingVisibility;
private Visibility _CarreVisibility;
private Care _selectedCare;
public int ProjectNumber
{
get
{
return _ProtjectNumber;
}
set
{
_ProtjectNumber = value;
OnPropertyChanged(nameof(ProjectNumber));
}
}
public string ProjectName
{
get
{
return _ProjectName;
}
set
{
_ProjectName = value;
OnPropertyChanged(nameof(ProjectName));
}
}
public Visibility TrackingVisibility
{
get
{
return _TrackingVisibility;
}
set
{
_TrackingVisibility = value;
OnPropertyChanged(nameof(_TrackingVisibility));
}
}
public Visibility CareVisibility
{
get
{
return _CarreVisibility;
}
set
{
_CarreVisibility = value;
OnPropertyChanged(nameof(CareVisibility));
}
}
public void TrackingView(RoutedEventArgs e)
{
TrackingVisibility = Visibility.Visible;
CareVisibility = Visibility.Collapsed;
}
public void CareView(RoutedEventArgs e)
{
TrackingVisibility = Visibility.Collapsed;
CareVisibility = Visibility.Visible;
}
public ObservableCollection<Care> CareList { get; set; }
public CareViewModel()
{
CareList = new ObservableCollection<Care>(); //List
StartCommand = new OurCommand<RoutedEventArgs>(Setting);
TrackingCommand = new OurCommand<RoutedEventArgs>(TrackingView);
CareCommand = new OurCommand<RoutedEventArgs>(CareView);
UpdateCommand = new OurCommand<RoutedEventArgs>(Bearbeitung);
TrackingVisibility = Visibility.Visible;
CareVisibility = Visibility.Collapsed;
}
public ICommand StartCommand { get; set; }
public ICommand TrackingCommand { get; set; }
public ICommand CareCommand { get; set; }
public ICommand UpdateCommand { get; set; }
public Care SelectedCare
{
get
{
return _selectedCare;
}
set
{
_selectedCare = value;
OnPropertyChanged(nameof(SelectedCare));
}
}
public void Setting(RoutedEventArgs e) // Add the Values to Datagrid
{
var projects = new Care();
projects.Projektnummer = _ProtjectNumber;
projects.Name = _ProjectName;
ProjectNumber = 0;
ProjectName = null;
SelectedCare = projects;
CareList.Add(projects);
}
public void Bearbeitung(RoutedEventArgs e) //Editing the value button
{
ProjectNumber = SelectedCare.Projektnummer;
ProjectName = SelectedCare.Name;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Class Care
public class Care
{
private int _Projektnummer;
private string _Name;
public int Projektnummer
{
get { return _Projektnummer;}
set { _Projektnummer = value;}
}
public string Name
{
get { return _Name;}
set { _Name = value;}
}
}
Thanks to everyone who wants to help me.
I've created some demo project from source you provided.
Selection of the row fills TextBoxes automatically
Add Button creates new row and make it able to edit
Save button applies changes in TextBoxes
X button deletes the row
0. MVVM Helpers
I don't know what is OurCommand but I'm using almost unchanged this one.
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
It supports command parameters and everything else related to ICommand.
To be able not to implement INotifyPropertyChanged in every View Model, I moved the implementation to the base class.
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
[CallerMemberName] is a compiler trick that allows to call OnPropertyChanged() without arguments. Compiler will fill it automatically with Caller Member Name :) - Property name.
1. Markup
Use Grid and relative positioning of controls. It will help you to make responsive design where layout looks fine for almost any area size.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Width="1200" Height="600">
<Window.DataContext>
<local:CareViewModel/>
</Window.DataContext>
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Margin" Value="5"/>
<Style.Triggers>
<DataTrigger Binding="{Binding EditingCare}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Button">
<Setter Property="Width" Value="75"/>
<Setter Property="Margin" Value="5"/>
</Style>
</Grid.Resources>
<StackPanel>
<TextBlock Text="Projektnumber:"/>
<TextBlock Text="Name:"/>
<TextBlock Text="Unterposition:"/>
<TextBlock Text="Describe:"/>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBox Text="{Binding EditingCare.ProjectNumber}"/>
<TextBox Text="{Binding EditingCare.Name}"/>
<TextBox Text="{Binding EditingCare.Subtitle}"/>
</StackPanel>
<TextBox TextWrapping="Wrap" Grid.Row="1" Grid.ColumnSpan="2" Text="{Binding EditingCare.Description}"/>
<DockPanel Margin="5" HorizontalAlignment="Right" Grid.Row="2" Grid.ColumnSpan="2">
<Button Content="Add" Command="{Binding AddCommand}" />
<Button Content="Save" Command="{Binding UpdateCommand}" />
</DockPanel>
</Grid>
<DataGrid Grid.Column="1" ItemsSource="{Binding CareList}" SelectedItem="{Binding SelectedCare}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding ProjectNumber}"/>
<DataGridTextColumn Header="Projektname" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Unterposition" Binding="{Binding Subtitle}" Width="*"/>
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="X" Command="{Binding DataContext.DeleteCommand,RelativeSource={RelativeSource AncestorType=DataGrid}}" CommandParameter="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
2. Data
To fix the initial problem I've implemented INotifyPropertyChanged for the Care class.
public class Care : NotifyPropertyChanged
{
private int _projectNumber;
private string _name;
private string _description;
private string _subtitle;
public int ProjectNumber
{
get => _projectNumber;
set
{
_projectNumber = value;
OnPropertyChanged();
}
}
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public string Subtitle
{
get => _subtitle;
set
{
_subtitle = value;
OnPropertyChanged();
}
}
public string Description
{
get => _description;
set
{
_description = value;
OnPropertyChanged();
}
}
public Care() { }
public Care(Care care)
{
ProjectNumber = care.ProjectNumber;
Name = care.Name;
Subtitle = care.Subtitle;
Description = care.Description;
}
}
2. View Model
I played a lot with code and came up with following one.
public class CareViewModel : NotifyPropertyChanged
{
private bool _trackingVisible;
private bool _careVisible;
private Care _selectedCare;
private Care _editingCare;
private ObservableCollection<Care> _careList;
private ICommand _addCommand;
private ICommand _trackingCommand;
private ICommand _careCommand;
private ICommand _updateCommand;
private ICommand _deleteCommand;
public ObservableCollection<Care> CareList
{
get => _careList;
set
{
_careList = value;
OnPropertyChanged();
}
}
public bool TrackingVisibility
{
get => _trackingVisible;
set
{
_trackingVisible = value;
OnPropertyChanged();
}
}
public bool CareVisibility
{
get => _careVisible;
set
{
_careVisible = value;
OnPropertyChanged();
}
}
public Care SelectedCare
{
get => _selectedCare;
set
{
_selectedCare = value;
EditingCare = value is Care care ? new Care(care) : value;
OnPropertyChanged();
}
}
public Care EditingCare
{
get => _editingCare;
set
{
_editingCare = value;
OnPropertyChanged();
}
}
public ICommand TrackingCommand => _trackingCommand ?? (_trackingCommand = new RelayCommand(parameter =>
{
TrackingVisibility = true;
CareVisibility = false;
}));
public ICommand CareCommand => _careCommand ?? (_careCommand = new RelayCommand(parameter =>
{
TrackingVisibility = false;
CareVisibility = true;
}));
// Add the Values to Datagrid
public ICommand AddCommand => _addCommand ?? (_addCommand = new RelayCommand(parameter =>
{
Care newItem = new Care();
CareList.Add(newItem);
SelectedCare = newItem;
}));
//Editing the value button
public ICommand UpdateCommand => _updateCommand ?? (_updateCommand = new RelayCommand(parameter =>
{
int index = CareList.IndexOf(SelectedCare);
CareList[index] = EditingCare;
SelectedCare = CareList[index];
}, parameter => EditingCare != null && SelectedCare != null));
public ICommand DeleteCommand => _deleteCommand ?? (_deleteCommand = new RelayCommand(parameter =>
{
if (parameter is Care care)
{
CareList.Remove(care);
}
}));
public CareViewModel()
{
CareList = new ObservableCollection<Care>();
TrackingVisibility = true;
CareVisibility = false;
}
}
3. Visibility
Among other changes pay attention to former Visibility properties now they are bool. And to attach it to controls here's converter that is already meintoined in Markup.
MVVM doesn't suggest to use UI-related types in View Model or Model unless you need it for special purpose.
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool v && v ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> null;
}
Usage of converter
<Button Visibility="{Binding TrackingVisibility, Converter={StaticResource BoolToVisibilityConverter}}"/>

UI DataBinding Only Updating when Initiated from Method Inside ViewModel

The desired flow of my project is:
Button click on UI triggers timer start.
Each timer tick changes the string bound to TextBox text.
UI updates to the new string automatically.
For a long time I thought I had a databinding problem because the UI wasn't updating, but then I found if I triggered the timer start via the view model rather than externally via main window code behind, the UI updated just fine. However this isn't the desired behavior, and I'm trying to understand why that's the case.
Full view (TextBlock Text="{Binding FileName}" is the property being updated):
<Window x:Class="MaterialDesignTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:ns="clr-namespace:MaterialDesignTest"
xmlns:local="clr-namespace:MaterialDesignTest"
Title="MainWindow" Height="350" Width="525"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="Auto"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="{DynamicResource MaterialDesignFont}">
<materialDesign:DialogHost Identifier="RootDialog" Loaded="DialogHost_Loaded">
<Grid>
<StackPanel>
<Button Width="100" x:Name="SearchRestore" Margin="0 150 0 0" Command="{x:Static materialDesign:DialogHost.OpenDialogCommand}" materialDesign:DialogHost.DialogClosingAttached="SearchRestore_OnDialogClosing"
Content="Restore" Click="SearchRestore_Click">
<Button.CommandParameter>
<StackPanel Margin="10">
<TextBlock Text="Restoring..." HorizontalAlignment="Center" Margin="0 0 0 15"/>
<TextBlock Text="{Binding FileName}" HorizontalAlignment="Center">
<TextBlock.DataContext>
<ns:ProgressViewModel />
</TextBlock.DataContext>
</TextBlock>
<Button Name="CircleButton" Margin="0 50 0 0" Style="{StaticResource MaterialDesignFloatingActionButton}" IsHitTestVisible="False"
materialDesign:ButtonProgressAssist.IsIndicatorVisible="{Binding IsSaving}"
materialDesign:ButtonProgressAssist.Value="{Binding SaveProgressButton}">
<materialDesign:PackIcon Height="24" Width="24" Foreground="White">
<materialDesign:PackIcon.Style>
<Style TargetType="materialDesign:PackIcon" BasedOn="{StaticResource {x:Type materialDesign:PackIcon}}">
<Setter Property="Kind" Value="CloudSync" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSaveComplete}" Value="True">
<Setter Property="Kind" Value="Check" />
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:0.8" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</materialDesign:PackIcon.Style>
</materialDesign:PackIcon>
</Button>
</StackPanel>
</Button.CommandParameter>
</Button>
</StackPanel>
</Grid>
</materialDesign:DialogHost>
Full view model (commenting out KickOffProgressTimer(); causing the UI to no longer update property FileName):
using MaterialDesignThemes.Wpf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace MaterialDesignTest
{
public class ProgressViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
System.Windows.Forms.Timer progressTimer;
private string _fileName;
public string FileName
{
get { return _fileName; }
set
{
if (value != _fileName)
{
_fileName = value;
OnPropertyChanged("FileName");
}
}
}
private double _saveProgressButton;
public double SaveProgressButton
{
get { return _saveProgressButton; }
set { this.MutateVerbose(ref _saveProgressButton, value, RaisePropertyChanged()); }
}
private bool _isSaveComplete;
public bool IsSaveComplete
{
get { return _isSaveComplete; }
private set { this.MutateVerbose(ref _isSaveComplete, value, RaisePropertyChanged()); }
}
private bool _isSaving;
public bool IsSaving
{
get { return _isSaving; }
private set { this.MutateVerbose(ref _isSaving, value, RaisePropertyChanged()); }
}
int progress = 0;
int cycles = 0;
public ProgressViewModel()
{
KickOffProgressTimer();
}
public void KickOffProgressTimer()
{
progressTimer = new System.Windows.Forms.Timer();
progressTimer.Tick += new EventHandler(progressTimerTick);
progressTimer.Interval = 140;
progressTimer.Start();
}
private async void progressTimerTick(object sender, EventArgs e)
{
if (progress < 100 && cycles < 2)
{
if (progress == 99)
{
cycles++;
progress = 0;
}
FileName = SelectRandomString();
IsSaveComplete = false;
IsSaving = true;
progress++;
SaveProgressButton = progress;
}
else
{
IsSaveComplete = true;
IsSaving = false;
progressTimer.Stop();
progressTimer.Enabled = false;
SaveProgressButton = 0;
await NonBlockingDelay(1750);
DialogHost.CloseDialogCommand.Execute(null, null);
}
}
async Task NonBlockingDelay(int value)
{
await Task.Delay(value);
}
private Action<PropertyChangedEventArgs> RaisePropertyChanged()
{
return args => PropertyChanged?.Invoke(this, args);
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
static string SelectRandomString()
{
var random = new Random();
var filenames = new List<string>(Directory.GetFiles(#"C:\Windows\System32\"));
int index = random.Next(filenames.Count);
return (filenames[index]);
}
}
}
Logic to kickoff timer in main window code behind:
private void CircleButtonClick(object sender, RoutedEventArgs e)
{
ProgressViewModel pvm = new ProgressViewModel();
CircleButton.DataContext = pvm;
pvm.KickOffProgressTimer();
}

WPF show "dummy" control until focused/clicked

I have a list of heavy Controls, which I don't want to render before the user interacts with them (one at a time).
I want to show a placeholder for each Control until the placeholder is clicked (preferably focused) and then render the real Control.
What I've tried looks like this:
<ContentControl x:Name="theControl">
<TextBox x:Name="TextBlock" Text="Placeholder right here."/>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFocused, ElementName=TextBlock}" Value="True">
<Setter Property="Content" >
<Setter.Value>
<Grid x:Name="theGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="CodeColumn"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Heavy control part1</TextBlock>
<TextBlock Grid.Column="1">heavy control part2</TextBlock>
</Grid>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Anyone knows a better approach or what I'm missing?
I don't know if this is a better solution, but you can create the heavy control in code and then remove/add children after a GotFocus event.
Add a GotFocus event to your TextBlock and put the TextBlock in a Grid
<Grid Name="myGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100*"/>
</Grid.RowDefinitions>
<TextBox x:Name="TextBlock" Grid.Column="0" Grid.Row="0" Text="Placeholder right here." GotFocus="TextBlock_GotFocus" />
</Grid>
Then in the cs file
private void TextBlock_GotFocus(object sender, RoutedEventArgs e)
{
createNewControl();
}
private void createNewControl()
{
Grid myOtherGrid = new Grid();
RowDefinition newRow1 = new RowDefinition();
newRow1.Height = new GridLength(100.0);
RowDefinition newRow2 = new RowDefinition();
newRow2.Height = new GridLength(100.0);
ColumnDefinition newColumn1 = new ColumnDefinition();
newColumn1.Width = new GridLength(50.0);
ColumnDefinition newColumn2 = new ColumnDefinition();
newColumn2.Width = new GridLength(50.0);
myOtherGrid.RowDefinitions.Add(newRow1);
myOtherGrid.RowDefinitions.Add(newRow2);
myOtherGrid.ColumnDefinitions.Add(newColumn1);
myOtherGrid.ColumnDefinitions.Add(newColumn2);
TextBox myOtherTextBlock1 = new TextBox();
myOtherTextBlock1.Text = "new block 1";
TextBox myOtherTextBlock2 = new TextBox();
myOtherTextBlock2.Text = "new block 1";
myOtherGrid.Children.Add(myOtherTextBlock1);
Grid.SetRow(myOtherTextBlock1, 0);
Grid.SetColumn(myOtherTextBlock1, 0);
myOtherGrid.Children.Add(myOtherTextBlock2);
Grid.SetRow(myOtherTextBlock2, 1);
Grid.SetColumn(myOtherTextBlock2, 1);
myGrid.Children.Remove(TextBlock);
myGrid.Children.Add(myOtherGrid);
}
This is the general idea of what I managed to get working.
public partial class PlaceHolder : UserControl
{
private bool m_isReadOnly;
private object m_PlaceholdeContent;
private bool m_hasValue;
private object m_realContent;
public PlaceHolder()
{
InitializeComponent();
GotFocus += OnGotFocus;
}
private void OnGotFocus(object sender, RoutedEventArgs routedEventArgs)
{
GotFocus -= OnGotFocus;
if (!RealContentIsUsed)
{
RealContentIsUsed = true;
Content = RealContent;
}
}
private bool RealContentIsUsed { get; set; }
public object RealContent
{
get { return m_realContent; }
set
{
m_realContent = value;
if (IsReadOnly || HasValue)
{
Content = m_realContent;
}
}
}
public object PlaceholdeContent
{
get { return m_PlaceholdeContent; }
set
{
m_PlaceholdeContent = value;
if (!RealContentIsUsed)
{
Content = m_PlaceholdeContent;
}
}
}
public bool IsReadOnly
{
get { return m_isReadOnly; }
set
{
m_isReadOnly = value;
if (value && !RealContentIsUsed)
{
Content = RealContent;
RealContentIsUsed = true;
}
}
}
public bool HasValue
{
get { return m_hasValue; }
set
{
m_hasValue = value;
if (HasValue && RealContentIsUsed == false)
{
Content = RealContent;
RealContentIsUsed = true;
}
}
}
}

How do you animate a line on a canvas in C#?

How would you make a line slowly draw across the screen?
I am trying to animate a line on a canvas in a C#/WPF project.
I would like to use C# code and not XAML.
I Have a running sample that uses the MVVM Pattern and creates Lines within a ListBox that has a Canvas as its ItemsPanel.
I actually made it for this question, but the OP kind of dissapeared and never contacted me about it.
This is what it looks like in my computer:
The main part of it is this:
<ListBox ItemsSource="{Binding}" x:Name="lst" Height="500" Width="500">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="FocusVisualStyle">
<Setter.Value>
<Style TargetType="Control">
<Setter Property="Opacity" Value="0"/>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Line X1="{Binding X1}" Y1="{Binding Y1}"
X2="{Binding X2}" Y2="{Binding Y2}"
StrokeThickness="{Binding Thickness}"
Opacity="{Binding Opacity}"
x:Name="Line">
<Line.Stroke>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="{Binding Color1}" Offset="0"/>
<GradientStop Color="{Binding Color2}" Offset="1"/>
</LinearGradientBrush>
</Line.Stroke>
</Line>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Effect" TargetName="Line">
<Setter.Value>
<DropShadowEffect Color="CornflowerBlue" ShadowDepth="3" BlurRadius="10"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
ViewModel:
public class LineViewModel : INotifyPropertyChanged
{
#region Timer-based Animation
private System.Threading.Timer Timer;
private static Random Rnd = new Random();
private bool _animate;
public bool Animate
{
get { return _animate; }
set
{
_animate = value;
NotifyPropertyChanged("Animate");
if (value)
StartTimer();
else
StopTimer();
}
}
private int _animationSpeed = 1;
public int AnimationSpeed
{
get { return _animationSpeed; }
set
{
_animationSpeed = value;
NotifyPropertyChanged("AnimationSpeed");
if (Timer != null)
Timer.Change(0, 100/value);
}
}
private static readonly List<int> _animationSpeeds = new List<int>{1,2,3,4,5};
public List<int> AnimationSpeeds
{
get { return _animationSpeeds; }
}
public void StartTimer()
{
StopTimer();
Timer = new Timer(x => Timer_Tick(), null, 0, 100/AnimationSpeed);
}
public void StopTimer()
{
if (Timer != null)
{
Timer.Dispose();
Timer = null;
}
}
private void Timer_Tick()
{
X1 = X1 + Rnd.Next(-2, 3);
Y1 = Y1 + Rnd.Next(-2, 3);
X2 = X2 + Rnd.Next(-2, 3);
Y2 = Y2 + Rnd.Next(-2, 3);
}
#endregion
#region Coordinates
private double _x1;
public double X1
{
get { return _x1; }
set
{
_x1 = value;
NotifyPropertyChanged("X1");
}
}
private double _y1;
public double Y1
{
get { return _y1; }
set
{
_y1 = value;
NotifyPropertyChanged("Y1");
}
}
private double _x2;
public double X2
{
get { return _x2; }
set
{
_x2 = value;
NotifyPropertyChanged("X2");
}
}
private double _y2;
public double Y2
{
get { return _y2; }
set
{
_y2 = value;
NotifyPropertyChanged("Y2");
}
}
#endregion
#region Other Properties
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private double _thickness;
public double Thickness
{
get { return _thickness; }
set
{
_thickness = value;
NotifyPropertyChanged("Thickness");
}
}
public Color Color1 { get; set; }
public Color Color2 { get; set; }
private double _opacity = 1;
public double Opacity
{
get { return _opacity; }
set
{
_opacity = value;
NotifyPropertyChanged("Opacity");
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}));
}
#endregion
}
Edit: Source code now on GitHub
You will need to use a Storyboard and animate the Line.X2 and Line.Y2 Properties. See if this works for you.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas Name="myCanvas">
<Button Canvas.Left="248" Canvas.Top="222" Content="Button" Height="23" Name="button1" Width="75" Click="button1_Click" />
</Canvas>
</Window>
Button Click Event
private void button1_Click(object sender, RoutedEventArgs e)
{
Line line = new Line();
myCanvas.Children.Add(line);
line.Stroke = Brushes.Red;
line.StrokeThickness = 2;
line.X1 = 0;
line.Y1 = 0;
Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation(line.Y2 , 100, new Duration(new TimeSpan(0, 0, 1)));
DoubleAnimation da1 = new DoubleAnimation(line.X2, 100, new Duration(new TimeSpan(0, 0, 1)));
Storyboard.SetTargetProperty(da, new PropertyPath("(Line.Y2)"));
Storyboard.SetTargetProperty(da1, new PropertyPath("(Line.X2)"));
sb.Children.Add(da);
sb.Children.Add(da1);
line.BeginStoryboard(sb);
}

Stylizing graphs and tooltip of DataVisualization.Chart

I'm creating a UserControl component of type DataVisualization.Chart. I'm building component based on the example of url: http://www.dotnetcurry.com/ShowArticle.aspx?ID=553. This chart will generate charts of the type chosen as either columns, bars, lines, Pie ... In the construction of the component, i'm inserting the Data Source than is passed through another View. So far so good. The chart for all types are generated normally.
What I need to do now are 3 modifications in the component. But I can't find a way to resolve these modifications:
When the user places the mouse over the value generated by the graph, the ToolTip appears the value of the object .. What I need is that the ToolTip Text appear as IndependentValuePath, which would be the name + value in the following format: "Name (value)";
When a graph is generated, it inserts as a kind of label depending on the graph is the x axis or the y axis (as pictured). I need to remove it (Example in image);
For graphs of type Column (and probably others yet to be confirmed), the outline of the rectangle must be the same color as the internal color. Besides being in the same color, each rectangle, which will change color according to a pre-defined range, which will be passed as the DataSource of the previous View .. What would be this: if the value is below 200, the color is red, if between 200 and 350, will be yellow ... and so on. I'll have about 5 limits;
PS: the last THIS is the tooltip.
Does anyone know how I can stylize these graphs and tooltip?
Best regards,
Gustavo
Edit: Here's my UserControl Chart:
<UserControl x:Class="Library.Core.GUI.WPF.Controls.ChartUCControl"
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:ct="clr-namespace:Library.Core.GUI.WPF.Controls"
xmlns:chart="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
x:Name="ChartUCControl1">
<UserControl.Resources>
<Style TargetType="chart:ColumnDataPoint">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type chart:ColumnDataPoint}">
<!--<Border BorderThickness="0" Background="Red" ToolTip="asdfasdf"/>-->
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Width="auto"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" >
<chart:Chart Name="chartView" Grid.Row="0" Title="{Binding TitleGraphic, Mode=TwoWay, ElementName=ChartUCControl1, UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.IsDeferredScrollingEnabled="True"
Width="{Binding WidthScrollViewer, Mode=TwoWay, ElementName=ChartUCControl1, UpdateSourceTrigger=PropertyChanged}"
MinWidth="{Binding MinWidthScrollViewer, Mode=TwoWay, ElementName=ChartUCControl1, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="{Binding HorizontalAlignmentGraphic, Mode=TwoWay, ElementName=ChartUCControl1, UpdateSourceTrigger=PropertyChanged}" >
<chart:Chart.Series>
<chart:ColumnSeries >
<ToolTipService.ToolTip>
<ContentControl Content="asdçlfkj"/>
</ToolTipService.ToolTip>
<chart:ColumnSeries.IndependentAxis>
<chart:CategoryAxis Orientation="X" Visibility="Visible" Height="0"/>
</chart:ColumnSeries.IndependentAxis>
</chart:ColumnSeries>
</chart:Chart.Series>
<!--<ToolTipService.ToolTip>
<ContentControl Content="{TemplateBinding IndependentValue}" />
</ToolTipService.ToolTip>-->
<!--PreviewMouseMove="chartView_PreviewMouseMove"-->
<!--<chart:Chart.Series>
<chart:ColumnSeries>
<chart:DataPointSeries.DataPointStyle>
<Style TargetType="chart:ColumnDataPoint">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type chart:ColumnDataPoint}">
<Border BorderBrush="Black" >
<ToolTipService.ToolTip>
<ContentControl Content="{Binding ToolTip}"/>
</ToolTipService.ToolTip>
<Border BorderBrush="Black" BorderThickness="1"/>
<ToolTipService.ToolTip>
<ContentControl Content="asçldfkj"/>
</ToolTipService.ToolTip>
</Border>
<ToolTip Content="çlkjasf"/>
<Rectangle>
<Rectangle.Fill>
<SolidColorBrush Color="Blue"/>
</Rectangle.Fill>
</Rectangle>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</chart:DataPointSeries.DataPointStyle>
</chart:ColumnSeries>
</chart:Chart.Series>-->
<!--<Style TargetType="{x:Type chart:ColumnDataPoint}">
<Setter Property="Background" Value="Orange" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type chart:ColumnSeries}">
<Border BorderBrush="Azure" BorderThickness="10" Opacity="10" x:Name="Root">
<Grid Background="Black">
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="Black" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Border BorderBrush="#ccffffff" BorderThickness="1">
<Border BorderBrush="#77ffffff" BorderThickness="1" />
</Border>
<Rectangle x:Name="SelectionHighlight" Fill="Red" Opacity="0" />
<Rectangle x:Name="MouseOverHighlight" Fill="White" Opacity="0" />
</Grid>
<ToolTipService.ToolTip>
<ContentControl Content="as" />
</ToolTipService.ToolTip>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>-->
<!--<ToolTipService.ToolTip>
<ContentControl Content="{Binding Identificacao}"/>
</ToolTipService.ToolTip>-->
<!--<Grid>
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Blue" Offset="0"/>
<GradientStop Color="Black" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>-->
<chart:Chart.Axes>
<chart:LinearAxis Orientation="X" Interval="{Binding IntervalAxis, ElementName=ChartUCControl1, Mode=TwoWay}"
MouseEnter="chartView_PreviewMouseMove"
Maximum="{Binding MaximumAxis, ElementName=ChartUCControl1, Mode=TwoWay}"/>
<!-- This section configures labels of X Orientation -->
<chart:CategoryAxis Orientation="X" Visibility="Hidden"/>
</chart:Chart.Axes>
<chart:Chart.LegendStyle>
<Style TargetType="Control">
<Setter Property="Width" Value="0"/>
<Setter Property="Height" Value="0"/>
</Style>
</chart:Chart.LegendStyle>
<chart:Chart.PlotAreaStyle>
<Style TargetType="Grid">
<Setter Property="Background" Value="Transparent"/>
<!--<Setter Property="ToolTip" Value="{x:Null}"/>-->
</Style>
</chart:Chart.PlotAreaStyle>
</chart:Chart>
</ScrollViewer>
</Grid>
</Grid>
</UserControl>
IN case of need to look my code-behind:
namespace Library.Core.GUI.WPF.Controls
{
public partial class ChartUCControl : UserControl, INotifyPropertyChanged
{
#region Constructors
public ChartUCControl()
{
InitializeComponent();
}
#endregion
#region ChartType Property
public static ChartTypes GetChartType(DependencyObject d)
{
return (ChartTypes)d.GetValue(ChartTypeProperty);
}
public static void SetChartType(DependencyObject d, ChartTypes value)
{
d.SetValue(ChartTypeProperty, value);
}
public static readonly DependencyProperty ChartTypeProperty =
DependencyProperty.RegisterAttached(
"ChartType",
typeof(ChartTypes),
typeof(ChartUCControl),
new FrameworkPropertyMetadata(ChartTypeChangedCallback)
);
private static void ChartTypeChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Chart instance = (d as Chart);
if (instance.IsNull())
return;
SetChartType(d, (ChartTypes)e.NewValue);
ChartUCControl instance2 = (d as ChartUCControl);
}
private ChartTypes chartType;
public ChartTypes ChartType
{
get { return chartType; }
set { chartType = value; }
}
#endregion
#region ChartAreaSelected Property
public static readonly DependencyProperty ChartAreaProperty =
DependencyProperty.RegisterAttached(
"ChartArea",
typeof(Boolean),
typeof(ChartUCControl),
new FrameworkPropertyMetadata(ChartAreaChangedCallback));
private static void ChartAreaChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ChartUCControl instance = (d as ChartUCControl);
if (instance.IsNull())
return;
instance.ChartArea = (Boolean)e.NewValue;
}
private Boolean chartArea;
public Boolean ChartArea
{
get { return chartArea; }
set
{
chartArea = value;
if (value)
{
cmbChart.SelectedItem = ChartTypes.Area;
DoRenderizeGraphic();
}
}
}
#endregion
#region ChartBarSelected Property
public static readonly DependencyProperty ChartBarProperty =
DependencyProperty.RegisterAttached(
"ChartBar",
typeof(Boolean),
typeof(ChartUCControl),
new FrameworkPropertyMetadata(ChartBarChangedCallback));
private static void ChartBarChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ChartUCControl instance = (d as ChartUCControl);
if (instance.IsNull())
return;
instance.ChartBar = (Boolean)e.NewValue;
}
private Boolean chartBar;
public Boolean ChartBar
{
get { return chartBar; }
set
{
chartBar = value;
if (value)
{
cmbChart.SelectedItem = ChartTypes.Bar;
DoRenderizeGraphic();
}
}
}
#endregion
#region ChartColumnSelected Property
public static readonly DependencyProperty ChartColumnProperty =
DependencyProperty.RegisterAttached(
"ChartColumn",
typeof(Boolean),
typeof(ChartUCControl),
new FrameworkPropertyMetadata(ChartColumnChangedCallback)
);
private static void ChartColumnChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ChartUCControl instance = (d as ChartUCControl);
if (instance.IsNull())
return;
instance.ChartColumn = (Boolean)e.NewValue;
}
private Boolean chartColumn;
public Boolean ChartColumn
{
get { return chartColumn; }
set
{
chartColumn = value;
if (value)
{
cmbChart.SelectedItem = ChartTypes.Columns;
DoRenderizeGraphic();
}
}
}
#endregion
#region ChartLinesSelected Property
public static readonly DependencyProperty ChartLinesProperty =
DependencyProperty.RegisterAttached(
"ChartLines",
typeof(Boolean),
typeof(ChartUCControl),
new FrameworkPropertyMetadata(ChartLinesChangedCallback));
private static void ChartLinesChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ChartUCControl instance = (d as ChartUCControl);
if (instance.IsNull())
return;
instance.ChartLines = (Boolean)e.NewValue;
}
private Boolean chartLines;
public Boolean ChartLines
{
get { return chartLines; }
set
{
chartLines = value;
if (value)
{
cmbChart.SelectedItem = ChartTypes.Lines;
DoRenderizeGraphic();
}
}
}
#endregion
#region FieldForIndependentValue Property
/// <summary>
/// String containing list of the only characters allowed, others will be filtered
/// </summary>
public String FieldForIndependentValue
{
get { return (String)GetValue(FieldForIndependentValueProperty); }
set { SetValue(FieldForIndependentValueProperty, value); }
}
public static readonly DependencyProperty FieldForIndependentValueProperty =
DependencyProperty.Register("FieldForIndependentValue", typeof(String), typeof(ChartUCControl));
#endregion
#region FieldForDependentValue Property
/// <summary>
/// String containing list of the only characters allowed, others will be filtered
/// </summary>
public String FieldForDependentValue
{
get { return (String)GetValue(FieldForDependentValueProperty); }
set { SetValue(FieldForDependentValueProperty, value); }
}
public static readonly DependencyProperty FieldForDependentValueProperty =
DependencyProperty.Register("FieldForDependentValue", typeof(String), typeof(ChartUCControl));
#endregion
#region TitleGraphic Property
public String TitleGraphic
{
get { return (String)base.GetValue(TitleGraphicProperty); }
set { base.SetValue(TitleGraphicProperty, value); }
}
public static readonly DependencyProperty TitleGraphicProperty =
DependencyProperty.RegisterAttached(
"TitleGraphic",
typeof(String),
typeof(ChartUCControl),
new FrameworkPropertyMetadata(null)
);
#endregion
#region MinWidthScrollViewer Property
public String MinWidthScrollViewer
{
get { return (String)base.GetValue(MinWidthScrollViewerProperty); }
set { base.SetValue(MinWidthScrollViewerProperty, value); }
}
public static readonly DependencyProperty MinWidthScrollViewerProperty =
DependencyProperty.RegisterAttached(
"MinWidthScrollViewer",
typeof(String),
typeof(ChartUCControl),
new FrameworkPropertyMetadata(null));
#endregion
#region Width ScrollViewer Property
public String WidthScrollViewer
{
get { return (String)base.GetValue(WidthScrollViewerProperty); }
set { base.SetValue(WidthScrollViewerProperty, value); }
}
public static readonly DependencyProperty WidthScrollViewerProperty =
DependencyProperty.RegisterAttached(
"WidthScrollViewer",
typeof(String),
typeof(ChartUCControl),
new FrameworkPropertyMetadata(null)
);
#endregion
#region ItemsSource Property
public IEnumerable ItemsSource
{
get { return (IEnumerable)base.GetValue(ItemsSourceProperty); }
set { base.SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.RegisterAttached(
"ItemsSource",
typeof(IEnumerable),
typeof(ChartUCControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region Methods
private void ImageButton_Click_1(object sender, RoutedEventArgs e)
{
DoRenderizeGraphic();
}
public void DoRenderizeGraphic()
{
while (this.chartView.Series.Count() - 1 >= 0)
this.chartView.Series.Remove(this.chartView.Series[0]);
DataPointSeries objChar = null;
if (!cmbChart.SelectedItem.IsNull())
switch ((ChartTypes)cmbChart.SelectedValue)
{
case ChartTypes.Bar:
this.chartView.Series.Add(new BarSeries());
objChar = this.chartView.Series[0] as BarSeries;
break;
case ChartTypes.Columns:
this.chartView.Series.Add(new ColumnSeries());
objChar = this.chartView.Series[0] as ColumnSeries;
break;
case ChartTypes.Pie:
this.chartView.Series.Add(new PieSeries());
objChar = this.chartView.Series[0] as PieSeries;
break;
case ChartTypes.Lines:
this.chartView.Series.Add(new LineSeries());
objChar = this.chartView.Series[0] as LineSeries;
break;
case ChartTypes.Area:
this.chartView.Series.Add(new AreaSeries());
objChar = this.chartView.Series[0] as AreaSeries;
break;
default:
break;
}
if (!objChar.IsNull())
{
objChar.IsSelectionEnabled = true;
objChar.DependentValuePath = FieldForDependentValue;
objChar.IndependentValuePath = FieldForIndependentValue;
objChar.ItemsSource = ItemsSource;
if (this.chartView.Axes.Count > 0
&& (!this.chartView.ActualAxes[0].IsNull() || !this.chartView.ActualAxes[0].DependentAxes.IsNull()))
foreach (var item in this.chartView.ActualAxes[0].DependentAxes)
{
this.chartView.ActualAxes[0].DependentAxes.Remove(item);
}
}
}
#endregion
#region Properties
private String[] chartTypesList;
public String[] ChartTypesList
{
get { return chartTypesList; }
set
{
chartTypesList = value;
OnPropertyChanged("ChartTypesList");
}
}
private String chartTypeSelectedValue;
public String ChartTypeSelectedValue
{
get { return chartTypeSelectedValue; }
set
{
chartTypeSelectedValue = value;
OnPropertyChanged("ChartTypeSelectedValue");
}
}
#endregion
#region INotifyPropertyChanged event and method
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
if (!PropertyChanged.IsNull())
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
#endregion
#region Preview Mouse events
private void chartView_PreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
var t = ((e.OriginalSource) as FrameworkElement).DataContext;
if (!t.IsNull() && e.OriginalSource.GetType().Equals(typeof(System.Windows.Shapes.Rectangle)))
{
Object a = null;
foreach (PropertyInfo item in ((e.OriginalSource) as FrameworkElement).DataContext.GetType().GetProperties())
{
a = item.GetValue(((e.OriginalSource) as FrameworkElement).DataContext, null);
break;
}
if (a == null)
a = "";
((FrameworkElement)(e.OriginalSource)).ToolTip = a;
}
}
#endregion
#region ReflectionReturn Seekers
public List<Control> FindAllControls(DependencyObject parent)
{
var list = new List<Control>() { };
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is Control)
{
list.Add(child as Control);
}
list.AddRange(FindAllControls(child));
}
return list;
}
public IEnumerable<FieldInfo> GetAllFields(Type t)
{
if (t.IsNull())
return Enumerable.Empty<FieldInfo>();
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
return t.GetFields(flags).Union(GetAllFields(t.BaseType));
}
#endregion
}
#region Enum
public enum ChartTypes
{
Bar,
Columns,
Pie,
Lines,
Area
}
#endregion
}
Answer 1
You can declare tooltips inside the ColumnDataPoint style. The simplest approach would be to add a new property to your model and bind to it.
For example, if the chart item class has 2 properties, add the 3rd property specially for tooltips:
public class ChartItem
{
public string Title { get; set; } // coil456
public double Value { get; set; } // 334
public string TooltipLabel
{
get { return string.Format("{0}({1})", this.Title, this.Value); } // coil456(334)
}
}
Then add a binding to the data point style:
<Style x:Key="ColumnDataPointStyle" TargetType="chart:ColumnDataPoint">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type chart:ColumnDataPoint}">
<Rectangle ToolTipService.ToolTip="{Binding TooltipLabel}">
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The changed line: ToolTipService.ToolTip="{Binding TooltipLabel}". I used a simple binding because I bind to the DataContext, not to the template.
Then change your code-behind so that the column series uses the style:
case ChartTypes.Columns:
objChar = new ColumnSeries();
objChar.DataPointStyle = (Style)this.Resources["ColumnDataPointStyle"];
this.chartView.Series.Add(objChar);
break;
Similarly, you can add tooltips to axes by using the AxisLabelStyle property and a style for NumericAxisLabel.
Answer 3
Everything in the same way: add a property to the model and bind to it.
C# model
public class ChartItem
{
public string Title { get; set; }
public double Value { get; set; }
public string TooltipLabel
{
get { return string.Format("{0}({1})", this.Title, this.Value); }
}
public SolidColorBrush ColumnBrush
{
get
{
if (this.Value < 200)
{
return Brushes.Red;
}
else if (this.Value < 350)
{
return Brushes.Yellow;
}
else
{
return Brushes.Green;
}
}
}
}
Template
<Style x:Key="ColumnDataPointStyle" TargetType="chart:ColumnDataPoint">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type chart:ColumnDataPoint}">
<Rectangle ToolTipService.ToolTip="{Binding TooltipLabel}" Fill="{Binding ColumnBrush}">
</Rectangle>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Categories