I have created bar chart control in wpf using system.windows.controls.datavisualization.toolkit dll. I want to specify the minimum and maximum for the Y axis.
Here the Bar-chart
`
<Grid >
<barChartToolkit:Chart Height="280" HorizontalAlignment="Stretch" Title="Resource Availability" Name="columnChart" Background="White" VerticalAlignment="Stretch" Width="360">
<barChartToolkit:ColumnSeries DependentValuePath="Value" IndependentValuePath="Name" ItemsSource="{Binding}" Title="Resources" />
</barChartToolkit:Chart>
</Grid>
`
Now I'm created the list and binded DataContext of the chart
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
showColumnChart();
}
private void showColumnChart()
{
List<BarCHartData> valueList = new List<BarCHartData>();
valueList.Add(new BarCHartData() { Name = "Developer", Value = 10 });
valueList.Add(new BarCHartData() { Name = "Tester", Value = 20 });
valueList.Add(new BarCHartData() { Name = "QA", Value = 30 });
columnChart.DataContext = valueList;
}
}
public class BarCHartData
{
public string Name { get; set; }
public int Value { get; set; }
}
here my bar chart plotting like below image
I have tried with below code
<Window x:Class="WpfToolkitChart.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:barChartToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit">
<Grid >
<barChartToolkit:Chart Height="280" HorizontalAlignment="Stretch" Title="Resource Availability" Name="columnChart" Background="White" VerticalAlignment="Stretch" Width="360">
<barChartToolkit:ColumnSeries DependentValuePath="Value" IndependentValuePath="Name" ItemsSource="{Binding}" Title="Resources" />
<barChartToolkit:Chart.Axes>
<barChartToolkit:LinearAxis Orientation="Y" Minimum="0" Maximum="100"/>
</barChartToolkit:Chart.Axes>
</barChartToolkit:Chart>
</Grid>
but this code removing the grid line of the graph like below image
how can i set the maximum and minimum value to the Y-Axis with the gridlines?
You just need to set ShowGridLines="True" in your LinearAxis:
XAML:
<Window
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:WpfApp55"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
x:Class="WpfApp55.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<x:Array x:Key="array1" Type="{x:Type local:BarChartData}">
<local:BarChartData Name="Developer" Value="25" />
<local:BarChartData Name="Tester" Value="50" />
<local:BarChartData Name="QA" Value="75" />
</x:Array>
</Window.Resources>
<Grid>
<chartingToolkit:Chart Title="Sample Chart">
<chartingToolkit:Chart.Axes>
<chartingToolkit:LinearAxis Minimum="0"
Maximum="100"
Orientation="Y"
ShowGridLines="True" />
</chartingToolkit:Chart.Axes>
<chartingToolkit:ColumnSeries DependentValuePath="Value"
IndependentValuePath="Name"
ItemsSource="{StaticResource array1}"/>
</chartingToolkit:Chart>
</Grid>
If you want to limit the values for display, you could use a converter and DependentValueBinding instead of DependentValuePath
public class RangeConverter : IValueConverter
{
public double Min { get; set; }
public double Max { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var d = System.Convert.ToDouble(value, culture);
return Math.Max(Min, Math.Min(Max, d));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Converter resource:
<local:RangeConverter x:Key="rangeConverter" Max="100" Min="0"/>
Usage
<DVC:ColumnSeries
DependentValueBinding="{Binding Value,Converter={StaticResource rangeConverter}}"
IndependentValuePath="Name"
ItemsSource="{Binding}"
Title="Resources"/>
Related
I have an issue with setting the visibility of a control when specifying the context using ElementName.
For some reason wpf seems to behave differently when using ElementName, versus using the context that is set from the code-behind.
The following code works fine. The collection contains two elements, and the button is set to visible.
MainWindow.xaml
<Window x:Class="WpfObservableCollectionVisibility.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:WpfObservableCollectionVisibility"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" x:Name="MyWindow">
<Window.Resources>
<ResourceDictionary>
<local:EmptyListVisibilityConverter x:Key="EmptyListVisibilityConverter"></local:EmptyListVisibilityConverter>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Button Height="50" Width="100" Content="Button" VerticalAlignment="top" Visibility="{Binding MenuItems, Converter={StaticResource EmptyListVisibilityConverter}}"></Button>
<Grid Width="200" Height="200">
<ItemsControl ItemsSource="{Binding MenuItems}" Focusable="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static readonly DependencyProperty MenuItemsProperty =
DependencyProperty.Register(
"MenuItems", typeof(ObservableCollection<MyItem>),
typeof(MainWindow),
new FrameworkPropertyMetadata(default (ObservableCollection<MyItem>),FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
public MainWindow()
{
InitializeComponent();
MenuItems = new ObservableCollection<MyItem>();
MenuItems.Add(new MyItem("Entry 1"));
MenuItems.Add(new MyItem("Entry 2"));
DataContext = this;
}
public ObservableCollection<MyItem> MenuItems
{
get => (ObservableCollection<MyItem>) GetValue(MenuItemsProperty);
set => SetValue(MenuItemsProperty, value);
}
}
public class MyItem
{
public MyItem(string name)
{
this.Name = name;
}
public string Name { get; set; }
}
public class EmptyListVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Trace.WriteLine($"Do conversion");
if (value == null)
{
Trace.WriteLine($"Conversion value was null");
return Visibility.Collapsed;
}
else
{
ICollection list = value as ICollection;
if (list != null)
{
if (list.Count == 0)
{
Trace.WriteLine($"Count is zero");
return Visibility.Hidden;
}
else
{
Trace.WriteLine($"Count is greater than zero");
return Visibility.Visible;
}
}
else
{
Trace.WriteLine($"Was not a collection");
return Visibility.Visible;
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
However, when I set the ElementName context for the ItemsControl and Button, the ItemsControl still displays fine, but the Button is not shown, because the converter for some reason receives an empty collection.
<Button Height="50" Width="100" Content="Button" VerticalAlignment="top" Visibility="{Binding MenuItems, ElementName=MyWindow, Converter={StaticResource EmptyListVisibilityConverter}}"></Button>
...
<ItemsControl ItemsSource="{Binding MenuItems, ElementName=MyWindow}" Focusable="False">
I tried debugging it, but I don't understand what is wrong. I don't know where this empty collection comes from. I also don't understand why the ItemsControl works fine. I feel like either both should work, or neither.
I would like to add a footer to the ListView with the sum of two columns in his respective columns, Licenses and Scans, I found something similar (Here) but the footer is not being shown, if I add a 4 column into the GridViewthen the footer row is shown but without any values.The GridViewColumnHeadercontains a style which I would like to apply to the Footer as well (I have not pasted it to provide a Minimal, functional example)
This is the expected view:
xaml
<Window x:Class="WpfApp2.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:WpfApp2"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:utils="Helper"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<CollectionViewSource x:Key="ViewSource"
Source="{Binding DataList}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ToOrder" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Source={StaticResource ViewSource}}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<DockPanel>
<Grid DockPanel.Dock="Bottom">
<Grid.Resources>
<local:SumConverter x:Key="sumConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=Col1, Path=ActualWidth}"/>
<ColumnDefinition Width="{Binding ElementName=Col2, Path=ActualWidth}"/>
<ColumnDefinition Width="{Binding ElementName=Col3, Path=ActualWidth}"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1"
Text="Sum: "
FontWeight="Bold" />
<TextBlock Grid.Column="1"
Grid.Row="1"
Text="{Binding Path=Items, Converter={StaticResource sumConverter}, ConverterParameter=1}"/>
<TextBlock Grid.Column="2"
Grid.Row="1"
Text="{Binding Path=Items, Converter={StaticResource sumConverter}, ConverterParameter=2}"/>
<Line Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="3"
Stroke="Black"
X2="500"
Fill="Black"
VerticalAlignment="Center" />
</Grid>
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.View>
<GridView x:Name="OverviewGridView">
<GridViewColumn DisplayMemberBinding="{Binding Date, StringFormat=dd.MM.yyyy}"
Header="Date"
x:Name="Col1"/>
<GridViewColumn DisplayMemberBinding="{Binding LicenseCount}"
Header="Licenses"
x:Name="Col2"/>
<GridViewColumn DisplayMemberBinding="{Binding ScansCount}"
Header="Scans"
x:Name="Col3"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ViewModel
namespace WpfApp2
{
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using WpfApp2.Annotations;
public class OverView
{
public int ToGroup { get; set; } = 1;
public DateTime Date { get; set; }
public int LicenseCount { get; set; }
public int ScansCount { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
private int _totalLicenses;
private ObservableCollection<OverView> _dataList;
private int _totalScans;
public ViewModel()
{
OverView a = new OverView
{
Date = DateTime.Now.Subtract(new TimeSpan(1, 0, 0, 0)),
LicenseCount = 2,
ScansCount = 7
};
OverView b = new OverView
{
Date = DateTime.Now.Subtract(new TimeSpan(5, 0, 0, 0)),
LicenseCount = 3,
ScansCount = 2
};
OverView c = new OverView { Date = DateTime.Now, LicenseCount = 5, ScansCount = 4 };
OverView d = new OverView
{
Date = DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)),
LicenseCount = 1,
ScansCount = 3
};
DataList = new ObservableCollection<OverView> { a, b, c, d };
TotalLicenses = DataList.Sum(overview => overview.LicenseCount);
TotalScans = DataList.Sum(overview => overview.ScansCount);
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<OverView> DataList
{
get => _dataList;
set
{
_dataList = value;
OnPropertyChanged();
}
}
public int TotalLicenses
{
get => _totalLicenses;
set
{
_totalLicenses = value;
OnPropertyChanged();
}
}
public int TotalScans
{
get => _totalScans;
set
{
_totalScans = value;
OnPropertyChanged();
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
SumConverter
namespace WpfApp2
{
#region Using
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows.Data;
#endregion
public class SumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int sum = 0;
if (value is IEnumerable<object> total)
{
foreach (object o in total)
{
if (o is OverView overview)
{
int col = int.Parse((string)parameter);
sum += col == 1 ? overview.LicenseCount : overview.ScansCount;
}
}
return sum;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => 1;
}
}
Thanks in advance!
Edit, after further test, I had kind of success creating a new Property in the Overview object to group by it, then use a converter to sum the columns I need.
This is the result: The code is updated with the solution.
You cannot integrate it into the listview but you can put it below like this:
<StackPanel>
<GridViewRowPresenter DockPanel.Dock="Bottom"
Content="Sum"
Columns="{Binding ElementName=OverviewGridView, Path=Columns}">
</GridViewRowPresenter>
<ListView
HorizontalAlignment="Stretch"
ItemsSource="{Binding DataList}">
<ListView.View>
<GridView x:Name="OverviewGridView">
<GridViewColumn DisplayMemberBinding="{Binding Date}" Header="Date"/>
<GridViewColumn DisplayMemberBinding="{Binding LicenseCount}" Header="Licenses"/>
<GridViewColumn DisplayMemberBinding="{Binding ScansCount}" Header="Scans"/>
</GridView>
</ListView.View>
</ListView>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=OverviewGridView, Path=Columns[0].ActualWidth}"/>
<ColumnDefinition Width="{Binding ElementName=OverviewGridView, Path=Columns[1].ActualWidth}"/>
<ColumnDefinition Width="{Binding ElementName=OverviewGridView, Path=Columns[2].ActualWidth}"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Summe" Grid.Column="0" Margin="10,0"/>
<TextBlock Text="{Binding DataList, FallbackValue=0,Converter={StaticResource Summierer}, ConverterParameter=1}" Margin="10,0" Grid.Column="1"/>
<TextBlock Text="{Binding DataList, FallbackValue=0,Converter={StaticResource Summierer}, ConverterParameter=2}" Margin="10,0" Grid.Column="2"/>
</Grid>
</StackPanel>
this is my converter (needs rewriting!! I didnt put any effort in it)
public class SummUp: IValueConverter
{
#region Convert
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var rtn = 0;
var coll = (value as ObservableCollection<OverView>);
var param = System.Convert.ToInt32(parameter);
foreach (var item in coll)
{
if (param == 1)
{
rtn += item.LicenseCount;
}
else if (param == 2)
{
rtn += item.ScansCount;
}
}
return rtn;
}//END Convert
#endregion Convert
#region ConvertBack
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}//END ConvertBack
#endregion ConvertBack
}//END class boolToNotVisibilityConverter : IValueConverter
I closely follow step-by-step lvChart Customizing Tooltips in trying to build a custom tooltip for LineChart points. But I get empty tooltip content. Other than using ObservableValue for ChartValues type, my code is near identical to that used in the lvChart tutorial. I am not using any MV**.
Has anyone worked out the tutorial or apply custom tooltip on a LineChart?
Main.xaml
<lvc:CartesianChart.DataTooltip>
<local:MyTooltip />
</lvc:CartesianChart.DataTooltip>
Main.xaml.cs
MyTooltipContents = new ChartValues<MyTooltipContent>();
for (int i = 0; i < MyData.Count(); i++)
{
MyTooltipContents.Add(new MyTooltipContent
{
Name = "No" + i,
Value = "Foo"
});
}
var MyTooltipContentMapper = Mappers.Xy<MyTooltipContent>()
.X((value, index) => index)
.Y(value => 1);
//lets save the mapper globally
Charting.For<MyTooltipContent>(MyTooltipContentMapper);
DataContext = this;
MyTooltip.xaml
<UserControl x:Class="MyNamespace.MyTooltip"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyNamespace"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
d:DataContext="{d:DesignInstance local:MyTooltip}"
Background="#E4555555" Padding="20 10" BorderThickness="2" BorderBrush="#555555">
<ItemsControl ItemsSource="{Binding Data.Points}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type lvc:DataPointViewModel}">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Name"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Value"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{Binding ChartPoint.Instance.(local:MyTooltipContent.Name)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White" />
<TextBlock Grid.Column="2" Text="{Binding ChartPoint.Instance.(local:MyTooltipContent.Value)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
MyToolTip.xaml.cs
public partial class MyTooltip : IChartTooltip
{
private TooltipData _data;
public MyTooltip()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public TooltipData Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged("Data");
}
}
public TooltipSelectionMode? SelectionMode { get; set; }
protected virtual void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MyTooltipContent.cs
public class MyTooltipContent
{
public string Name { get; set; }
public string Value { get; set; }
}
I copied your example, and it works for me.
You might want to look at your MyTooltipContents declaration on your Main.xaml.cs, which should be a public property:
public ChartValues<MyTooltipContent> MyTooltipContents { get; set; }
I don't know exactly what gave you your line series, but I used this one on my MainWindow.xaml (corresponds to your Main.xaml):
<Window x:Class="MyToolTipExample.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:MyToolTipExample"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<lvc:CartesianChart LegendLocation="Right">
<lvc:CartesianChart.Series>
<lvc:LineSeries Title="Customers" Values="{Binding MyTooltipContents}"></lvc:LineSeries>
</lvc:CartesianChart.Series>
<lvc:CartesianChart.DataTooltip>
<local:MyTooltip/>
</lvc:CartesianChart.DataTooltip>
</lvc:CartesianChart>
</Grid>
</Window>
Note that MyTooltipContents is bound to the LineSeries.
EDIT: included screenshot.
Below, the full code example (.Net Framework 4.8 and LiveCharts 0.9.7):
1 - MainWindow.xaml.cs (the corresponding MainWindow.xaml is above):
namespace MyToolTipExample
{
public partial class MainWindow : Window
{
public ChartValues<MyTooltipContent> MyTooltipContents { get; set; }
public MainWindow()
{
InitializeComponent();
MyTooltipContents = new ChartValues<MyTooltipContent>();
for (int i = 0; i < 50; i++)
{
MyTooltipContents.Add(new MyTooltipContent
{
Name = $"No{i}",
Value = "Foo"
});
}
var MyTooltipContentMapper = Mappers.Xy<MyTooltipContent>()
.X((value, index) => index)
.Y(value => 1);
//lets save the mapper globally
Charting.For<MyTooltipContent>(MyTooltipContentMapper);
DataContext = this;
}
}
}
2 - the UserControl MyTooltip.xaml:
<UserControl x:Class="MyToolTipExample.MyTooltip"
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:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:local="clr-namespace:MyToolTipExample"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:MyTooltip}"
Background="#45555555" Padding="20 10" BorderThickness="2" BorderBrush="#555555">
<ItemsControl ItemsSource="{Binding Data.Points}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type lvc:DataPointViewModel}">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Name"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Value"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{Binding ChartPoint.Instance.(local:MyTooltipContent.Name)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White" />
<TextBlock Grid.Column="2" Text="{Binding ChartPoint.Instance.(local:MyTooltipContent.Value)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
3 - Code-behind the UserControl MyTooltip.xaml.cs:
using LiveCharts;
using LiveCharts.Wpf;
using System.ComponentModel;
using System.Windows.Controls;
namespace MyToolTipExample
{
public partial class MyTooltip : UserControl, IChartTooltip
{
public MyTooltip()
{
InitializeComponent();
DataContext = this;
}
private TooltipData _data;
public event PropertyChangedEventHandler PropertyChanged;
public TooltipData Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged("Data");
}
}
public TooltipSelectionMode? SelectionMode { get; set; }
protected virtual void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
4 - Data model MyTooltipContent.cs (used by public property ChartValues):
namespace MyToolTipExample
{
public class MyTooltipContent
{
public string Name { get; set; }
public string Value { get; set; }
}
}
5 - Package.config:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="LiveCharts" version="0.9.7" targetFramework="net48" />
<package id="LiveCharts.Wpf" version="0.9.7" targetFramework="net48" />
</packages>
6 - The final solution structure should look something like this:
The application would be crashed when I used the PieChart to customize the ToolTip?
<Window x:Class="MyToolTipExample.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:MyToolTipExample"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>`enter code here`
<lvc:PieChart>
<lvc:PieChart.Series>
<lvc:PieSeries Fill="Green" Stroke="{x:Null}" StrokeThickness="0" />
</lvc:PieChart.Series>
<lvc:PieChart.DataTooltip>
<local:MyTooltip/>
</lvc:PieChart.DataTooltip>
</lvc:PieChart>
</Grid>
</Window>
I am having issues with an image not showing in a WPF control using a Converter to create the image from a local directory. Any direction on what I am doing wrong would be appreciated.
XAML
<UserControl x:Class="Coms.Views.ImageDetailView"
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:c="clr-namespace:Coms.Converter"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300" Background="Wheat">
<Control.Resources>
<c:ImageDetailConverter x:Key="converter" />
</Control.Resources>
<ListBox ItemsSource="{Binding icList}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Image Name="imgBox" Width="auto" Height="auto" Grid.Column="0" Source="{Binding Converter={StaticResource converter}}" />
<TextBlock Name="txtBlock2" Grid.Column="1" Text="{Binding coordinates}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
class
public class ImageDetail
{
public string fileName { get; set; }
public string coordinates { get; set; }
}
Converter
public class ImageDetailConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
IRuntimeConfigurations configs = ServiceLocator.Current.GetInstance<IRuntimeConfigurations>();
return new Bitmap(Image.FromFile(Path.Combine(configs.rootImagePath, ((IImageDetail)value).fileName)));
}
}
Your converter returns a System.Drawing.Bitmap, which is WinForms, not WPF.
It should return a WPF ImageSource (e.g. a BitmapImage) instead:
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var imageDetail = (ImageDetail)value;
var configs = ServiceLocator.Current.GetInstance<IRuntimeConfigurations>();
var path = Path.Combine(configs.rootImagePath, imageDetail.fileName);
var uri = new Uri(path, UriKind.RelativeOrAbsolute);
return new BitmapImage(uri);
}
Note also that WPF provides built-in type conversion from Uri and string (containing a valid URI) to ImageSource, so that your converter could also return the uri or path value.
In my app I load image and verify I haven't too much blobs.
In additional, I put all images I loaded in list box and all image have a border.
Now, I want to change border color pet item according the Boolean value (true or false according numbers of blobs and their size).
If my image passed the border should be green, else red.
my relevent code:
public List<String> ImagePath = new List<String>();
public MainWindow()
{
InitializeComponent();
lb_Images.ItemsSource = ImagePath;
}
private void bu_addImage_Click(object sender, RoutedEventArgs e)
{
addImageToListBox();
}
private void addImageToListBox()
{
String imagePath = getImage();
// verfiy img haven't too much blobs
Boolean passed = imagePassed(imagePath);
// add the image to the list box
// I want to change border according passed value
// True - green; False- red.
ImagePath.Add(imagePath);
lb_Images.Items.Refresh();
}
XAML:
<Window x:Class="forQuestionWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="216" Width="519">
<Window.Resources>
<DataTemplate x:Key="ImageGalleryDataTemplate">
<Grid>
<Border BorderBrush="Green" BorderThickness="2" Width="120" Height="120" Padding="5" Margin="5" CornerRadius="6">
<Image Source="{Binding}" Stretch="Fill" HorizontalAlignment="Center">
<Image.ToolTip>
<Grid>
<Image Source="{Binding}" Stretch="Fill" HorizontalAlignment="Center" Height="200" Width="200" />
</Grid>
</Image.ToolTip>
</Image>
</Border>
</Grid>
</DataTemplate>
<ItemsPanelTemplate x:Key="ImageGalleryItemsPanelTemplate">
<UniformGrid Rows="1" Columns="25" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</Window.Resources>
<Grid>
<Canvas Height="177" HorizontalAlignment="Left" Name="canvas1" VerticalAlignment="Top" Width="497">
<ListBox Canvas.Left="6" Canvas.Top="5" Height="166" Name="lb_Images" Width="441"
ItemTemplate="{StaticResource ImageGalleryDataTemplate}"
ItemsSource="{Binding Path=ImagePath}"
ItemsPanel="{StaticResource ImageGalleryItemsPanelTemplate}">
</ListBox>
<Button Canvas.Left="453" Canvas.Top="26" Content="Add" Height="64" Name="bu_addImage" Width="38" Click="bu_addImage_Click" />
</Canvas>
</Grid>
</Window>
For example, in the below image latest two items need to be with red border:
Thank you on any help!
AsfK
Instead of binding to imagePath (string) you can define a class:
public class ImageStuff {
public string ImagePath {get; set;}
public bool Passed {get; set;}
}
and add instance of this to the listbox.
Then, you can use a converter for your border like this:
<Border BorderThickness="2" Width="120" Height="120" Padding="5" Margin="5" CornerRadius="6" BorderBrush="{Binding Path=Passed, Mode=OneWay, Converter={StaticResource borderConverter}}">
where borderConverter is something like this:
public class borderConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value is bool)
{
if (!(bool)value == true)
return new SolidColorBrush(Colors.Red);
}
return new SolidColorBrush(Colors.Green);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw NotImplementedException();
}
}
You have to include borderConverter in your resources like this:
<Window.Resources>
<src:borderConverter x:Key="borderConverter" />
</Window.Resources>
where src is the namespace.
If you want to change property of ImageStuff class dinamically you have to implement INotifyPropertyChanged so the binding will be updated.
You can do it without Converter, just add DataTrigger with Tag property to DataTemplate. This can affect to the performance because the Converter will work little much longer.
Add this class:
public class MyImage
{
public string ImagePath
{
get;
set;
}
public bool Flag
{
get;
set;
}
}
And in code-behind use like this:
public List<MyImage> ImagePath = new List<MyImage>();
Full example:
XAML
<DataTemplate x:Key="ImageGalleryDataTemplate">
<Grid>
<Border Name="MyBorder" BorderBrush="#FFFF9800" BorderThickness="1" Width="120" Height="120" Padding="5" Margin="5" CornerRadius="6">
<Image Name="MyImage" Tag="{Binding Path=Flag}" Source="{Binding Path=ImagePath}" Stretch="Fill" HorizontalAlignment="Center">
<Image.ToolTip>
<Grid>
<Image Source="{Binding Path=ImagePath}" Stretch="Fill" HorizontalAlignment="Center" Height="200" Width="200" />
</Grid>
</Image.ToolTip>
</Image>
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Tag, ElementName=MyImage}" Value="True">
<Setter TargetName="MyBorder" Property="BorderBrush" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Tag, ElementName=MyImage}" Value="False">
<Setter TargetName="MyBorder" Property="BorderBrush" Value="Green" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Code-behind
public partial class MainWindow : Window
{
int imageNumber = 0;
public List<MyImage> ImagePath = new List<MyImage>();
public MainWindow()
{
InitializeComponent();
lb_Images.ItemsSource = ImagePath;
}
private void bu_addImage_Click(object sender, RoutedEventArgs e)
{
addImageToListBox();
}
private void addImageToListBox()
{
imageNumber++;
if (imageNumber == 4) imageNumber = 0;
string directoryPath = AppDomain.CurrentDomain.BaseDirectory;
// load input image
string ImageFilename = directoryPath + "img";
ImageFilename += imageNumber.ToString();
ImageFilename += ".jpg";
ImagePath.Add(new MyImage
{
ImagePath = ImageFilename,
Flag = false
});
lb_Images.Items.Refresh();
}
}
public class MyImage
{
public string ImagePath
{
get;
set;
}
public bool Flag
{
get;
set;
}
}
In order to the properties of MyImage class successfully changed, you need to implement the INotifyPropertyChanged interface.
You neeed to write BoolToColorConverter for this purpose. Find following converter code.
public sealed class BoolToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool bValue = (bool)value;
if (bValue)
return Color.Green;
else
return Color.Red;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Color color = (Color)value;
if (color == Color.Green)
return true;
else
return false;
}
}