I have a chart inside a user control (Chart UC), which is just like the LiveCharts zooming and panning example, so the Chart UC has bindings to it's code behind. I have an other user control (let's call it Item UC), which contains 2 Chart UCs. The Item UC has bindings and commands, all of which works with its view model (Item VM). What I haven't been able to figure out, how to set up the connection between the Chart UCs' Series property and the Item VM.
The Chart UC xaml:
<UserControl x:Class="responsive_ui_test.User_Controls.ZoomingAndPanning"
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:responsive_ui_test.User_Controls"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:zoomingAndPanning="clr-namespace:responsive_ui_test.User_Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<zoomingAndPanning:ZoomingModeCoverter x:Key="ZoomingModeCoverter"></zoomingAndPanning:ZoomingModeCoverter>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<lvc:CartesianChart Series="{Binding Series}" Zoom="{Binding ZoomingMode}" >
<lvc:CartesianChart.AxisX>
<lvc:Axis Name="X" Title="{Binding XAxisTitle}" LabelFormatter="{Binding XFormatter}"
Separator="{x:Static lvc:DefaultAxes.CleanSeparator}"/>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis Name="Y" Title="{Binding YAxisTitle}" LabelFormatter="{Binding YFormatter}"/>
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
<Button Grid.Row="1" Click="ResetZoomOnClick" Background="#FFDDDDDD">Reset Zoom</Button>
</Grid>
</UserControl>
Chart UC code behind:
namespace responsive_ui_test.User_Controls
{
public partial class ZoomingAndPanning : INotifyPropertyChanged
{
private ZoomingOptions _zoomingMode;
public ZoomingAndPanning()
{
InitializeComponent();
var gradientBrush = new LinearGradientBrush
{
StartPoint = new Point(0, 0),
EndPoint = new Point(0, 1)
};
gradientBrush.GradientStops.Add(new GradientStop(Color.FromRgb(33, 148, 241), 0));
gradientBrush.GradientStops.Add(new GradientStop(Colors.Transparent, 1));
ZoomingMode = ZoomingOptions.X;
DataContext = this;
}
public SeriesCollection Series { get; set; }
public Func<double, string> XFormatter { get; set; }
public Func<double, string> YFormatter { get; set; }
public string XAxisTitle { get; set; }
public string YAxisTitle { get; set; }
public ZoomingOptions ZoomingMode
{
get { return _zoomingMode; }
set
{
_zoomingMode = value;
OnPropertyChanged();
}
}
private void ToogleZoomingMode(object sender, RoutedEventArgs e)
{
switch (ZoomingMode)
{
case ZoomingOptions.None:
ZoomingMode = ZoomingOptions.X;
break;
case ZoomingOptions.X:
ZoomingMode = ZoomingOptions.Y;
break;
case ZoomingOptions.Y:
ZoomingMode = ZoomingOptions.Xy;
break;
case ZoomingOptions.Xy:
ZoomingMode = ZoomingOptions.None;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private ChartValues<ObservablePoint> GetData()
{
var r = new Random();
var min = 0;
var max = 65535;
var values = new ChartValues<ObservablePoint>();
for (var i = 1; i < 100; i++)
{
var seed = r.NextDouble();
var value = seed * (max - min) + min;
values.Add(new ObservablePoint(i, value));
}
return values;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ResetZoomOnClick(object sender, RoutedEventArgs e)
{
//Use the axis MinValue/MaxValue properties to specify the values to display.
//use double.Nan to clear it.
X.MinValue = double.NaN;
X.MaxValue = double.NaN;
Y.MinValue = double.NaN;
Y.MaxValue = double.NaN;
}
}
public class ZoomingModeCoverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch ((ZoomingOptions)value)
{
case ZoomingOptions.None:
return "None";
case ZoomingOptions.X:
return "X";
case ZoomingOptions.Y:
return "Y";
case ZoomingOptions.Xy:
return "XY";
default:
throw new ArgumentOutOfRangeException();
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Item UC xaml:
<UserControl x:Class="responsive_ui_test.User_Controls.DeviceTab"
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:responsive_ui_test.User_Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
xmlns:uc="clr-namespace:responsive_ui_test.User_Controls">
<Grid>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="250"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="2*" MinWidth="250"/>
</Grid.ColumnDefinitions>
<Grid Margin="0,0,5,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" MinHeight="80" MaxHeight="100"/>
<RowDefinition Height="*" MinHeight="80" MaxHeight="100"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<GroupBox Header="Port" Margin="0,0,0,10">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding FoundDevices}" Margin="0,0,0,4"></ComboBox>
<Grid Grid.Row="1" Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Command="{Binding ConnectCommand}" Margin="0,0,4,0">
Connect
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="10"/>
</Style>
</Button.Resources>
</Button>
<Button Command="{Binding ScanCommand}" Grid.Column="1" Margin="4,0,0,0">
Scan
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="10"/>
</Style>
</Button.Resources>
</Button>
</Grid>
</Grid>
</GroupBox>
<GroupBox Grid.Row="1" Header="Log File" Margin="0,0,0,10">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding FilePath}" IsReadOnly="True" Margin="0,0,0,4"/>
<Button Command="{Binding ChangeFilePathCommand}" Grid.Row="1" Margin="0,4,0,0">
Change
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="10"/>
</Style>
</Button.Resources>
</Button>
</Grid>
</GroupBox>
<GroupBox Grid.Row="2" Header="Temperature">
<uc:ZoomingAndPanning x:Name="lvcTemp"></uc:ZoomingAndPanning>
</GroupBox>
</Grid>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<Grid Grid.Column="2" Margin="5,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="*" MinHeight="30" MaxHeight="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Command="{Binding CloseTabCommand}" Name="btnClose" Margin="0" Width="100" HorizontalAlignment="Right" Background="Red" FontWeight="Bold">
CLOSE TAB
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="10"/>
</Style>
</Button.Resources>
</Button>
<Button Command="{Binding NewTabCommand}" Name="btnNew" Margin="0,0,110,0" Width="100" HorizontalAlignment="Right" Background="LawnGreen" FontWeight="Bold">
NEW TAB
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="10"/>
</Style>
</Button.Resources>
</Button>
<GroupBox Grid.Row="1" Header="Measurement">
<uc:ZoomingAndPanning Grid.Row="1" x:Name="lvcMeas"></uc:ZoomingAndPanning>
</GroupBox>
</Grid>
</Grid>
</Grid>
</UserControl>
Item UC's view model:
namespace responsive_ui_test.View_Models
{
class DeviceViewModel : ViewModelBase
{
int count = 0;
private readonly DelegateCommand _scanCommand;
private readonly DelegateCommand _connectCommand;
private readonly DelegateCommand _changeFilePathCommand;
private readonly DelegateCommand _closeTabCommand;
private readonly DelegateCommand _newTabCommand;
public ICommand ScanCommand => _scanCommand;
public ICommand ConnectCommand => _connectCommand;
public ICommand ChangeFilePathCommand => _changeFilePathCommand;
public ICommand CloseTabCommand => _closeTabCommand;
public ICommand NewTabCommand => _newTabCommand;
public DeviceViewModel()
{
_scanCommand = new DelegateCommand(OnScan);
_connectCommand = new DelegateCommand(OnConnect);
_changeFilePathCommand = new DelegateCommand(OnChangeFilePath);
_closeTabCommand = new DelegateCommand(OnCloseTab);
_newTabCommand = new DelegateCommand(OnNewTab);
_tabName = "Unconnected";
}
private string _filePath;
public string FilePath
{
get => _filePath;
set => SetProperty(ref _filePath, value);
}
private ObservableCollection<string> _foundDevices;
public ObservableCollection<string> FoundDevices
{
get => _foundDevices;
set => SetProperty(ref _foundDevices, value);
}
private string _tabName;
public string TabName
{
get => _tabName;
set => SetProperty(ref _tabName, value);
}
private void OnScan(object commandParameter)
{
FilePath = "scan command " + count.ToString();
count++;
}
private void OnConnect(object commandParameter)
{
FilePath = "connect command " + count.ToString();
count++;
}
private void OnChangeFilePath(object commandParameter)
{
FilePath = "change file path command " + count.ToString();
count++;
}
private void OnCloseTab(object commandParameter)
{
FilePath = "close tab command " + count.ToString();
count++;
}
private void OnNewTab(object commandParameter)
{
FilePath = "new tab command " + count.ToString();
count++;
}
private ChartValues<ObservablePoint> GetData()
{
var r = new Random();
var min = 0;
var max = 65535;
var values = new ChartValues<ObservablePoint>();
for (var i = 1; i < 50; i++)
{
var seed = r.NextDouble();
var value = seed * (max - min) + min;
values.Add(new ObservablePoint(i, value));
}
return values;
}
}
}
Main window xaml:
<Window x:Class="responsive_ui_test.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:uc="clr-namespace:responsive_ui_test.User_Controls"
xmlns:local="clr-namespace:responsive_ui_test"
mc:Ignorable="d"
Title="App" Height="550" Width="800" MinWidth="800" MinHeight="550">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="CustomHeaderTemplate">
<DockPanel LastChildFill="True">
<!--<Button Content="X" DockPanel.Dock="Right">
<Button.Template>
<ControlTemplate>
<Label FontWeight="Bold" Content="X" />
</ControlTemplate>
</Button.Template>
</Button>-->
<Label Content="{Binding TabName}" />
</DockPanel>
</DataTemplate>
</Grid.Resources>
<TabControl x:Name="tbCtrl" ItemsSource="{Binding Items}" Loaded="tbCtrl_Loaded" SelectionChanged="tbCtrl_SelectionChanged" ItemTemplate="{StaticResource CustomHeaderTemplate}">
<TabControl.ContentTemplate>
<DataTemplate>
<uc:DeviceTab/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Main window code behind:
namespace responsive_ui_test
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private ChartValues<ObservablePoint> GetData()
{
var r = new Random();
var min = 0;
var max = 65535;
var values = new ChartValues<ObservablePoint>();
for (var i = 1; i < 50; i++)
{
var seed = r.NextDouble();
var value = seed * (max - min) + min;
values.Add(new ObservablePoint(i, value));
}
return values;
}
private void tbCtrl_Loaded(object sender, RoutedEventArgs e)
{
var tabControlViewModel = new TabControlViewModel();
tabControlViewModel.Items.Add(new DeviceViewModel()
{
FilePath = "C:/1/",
FoundDevices = new ObservableCollection<string>()
{
"1", "2"
},
});
DataContext = tabControlViewModel;
tbCtrl.SelectedIndex = 0;
}
private void tbCtrl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (tbCtrl.SelectedIndex < 0) // if selection is empty
return;
var cnt = tbCtrl.Items.Count;
Debug.WriteLine("Item count: " + cnt);
Debug.WriteLine("Selected Index: " + tbCtrl.SelectedIndex);
}
}
}
Tab contorl's view model:
namespace responsive_ui_test.View_Models
{
class TabControlViewModel : ViewModelBase
{
public ObservableCollection<DeviceViewModel> Items { get; } = new ObservableCollection<DeviceViewModel>();
}
}
What I need to have inside the DeviceViewModel is a chart SeriesCollection property for each chart inside the Item UC. It's just not clear to me how to get this connection in my situation. I've spent a lot of time trying around and evetything works except the charts. I appreciate any help a lot, thank you so much in advance!
Finally I found a solution. I just created 2 different LiveCharts user controls which are the same, but the Binding for the chart's SeriesCollection is named differently. This way, I can bind to both chart's SeriesCollection from the view model and it works great.
Related
All, I am having a lot of problems with displaying a custom tooltip for my LiveCharts control Here is my code and what I have tried.
Chart C# Code:
public partial class Chart : UserControl
{
public ChartData chartData { get; set; }
public Chart()
{
chartData = new ChartData();
InitializeComponent();
Loaded += Control_Loaded;
}
private void Control_Loaded(object sender, RoutedEventArgs e)
{
// build here
DataContext = this;
chartData.Formatter = value => value.ToString("C", CultureInfo.CurrentCulture);
}
}
Chart XAML:
<UserControl x:Class="LandlordTenantDatabaseWPF.Chart"
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:LandlordTenantDatabaseWPF"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:Chart}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="txtTitle" Grid.Row="0" Text="{Binding chartData.Title}" Foreground="#FF4B52E4" FontSize="36" TextAlignment="Center"/>
<lvc:CartesianChart x:Name="chart" Grid.Row="1" Series="{Binding chartData.SeriesCollection}" LegendLocation="Right" >
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Month" Labels="{Binding chartData.Labels}"></lvc:Axis>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis Title="Payments" LabelFormatter="{Binding chartData.Formatter}"></lvc:Axis>
</lvc:CartesianChart.AxisY>
<lvc:CartesianChart.DataTooltip>
<local:CustomersTooltip/>
</lvc:CartesianChart.DataTooltip>
</lvc:CartesianChart>
</Grid>
ToolTip C# Code:
public partial class CustomersTooltip : IChartTooltip
{
private TooltipData _data;
public CustomersTooltip()
{
InitializeComponent();
//LiveCharts will inject the tooltip data in the Data property
//your job is only to display this data as required
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));
}
}
ToolTip XAML:
<UserControl x:Class="LandlordTenantDatabaseWPF.CustomersTooltip"
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:wpf="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:local="clr-namespace:LandlordTenantDatabaseWPF"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
d:DataContext="{d:DesignInstance local:CustomersTooltip}"
Background="#E4555555" Padding="20 10" BorderThickness="2" BorderBrush="#555555">
<ItemsControl ItemsSource="{Binding Data.Points}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type wpf:DataPointViewModel}">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Title"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="LastName"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="Phone"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="PurchasedItems"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Stroke="{Binding Series.Stroke}" Fill="{Binding Series.Fill}"
Height="15" Width="15"></Rectangle>
<TextBlock Grid.Column="1" Text="{Binding ChartPoint.Instance.(local:CustomerVm.Name)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
<TextBlock Grid.Column="2" Text="{Binding ChartPoint.Instance.(local:CustomerVm.LastName)}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
<TextBlock Grid.Column="3" Text="{Binding ChartPoint.Instance.(local:CustomerVm.Phone),
StringFormat=Phone: {0}}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
<TextBlock Grid.Column="4" Text="{Binding ChartPoint.Instance.(local:CustomerVm.PurchasedItems),
StringFormat=Purchased Items: {0:N}}"
Margin="5 0 0 0" VerticalAlignment="Center" Foreground="White"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ChartData Class:
public class ChartData : INotifyPropertyChanged
{
private SeriesCollection seriesCollection;
public SeriesCollection SeriesCollection
{
get { return seriesCollection; }
set
{
if (seriesCollection != value)
{
seriesCollection = value;
OnPropertyChanged("seriesCollection");
}
}
}
private string[] labels;
public string[] Labels
{
get { return labels; }
set
{
if(labels != value)
{
labels = value;
OnPropertyChanged("labels");
}
}
}
private string title;
public string Title
{
get { return title; }
set
{
if(title != value)
{
title = value;
OnPropertyChanged("title");
}
}
}
private Func<double, string> formatter;
public Func<double, string> Formatter
{
get { return formatter; }
set
{
if(formatter != value)
{
formatter = value;
OnPropertyChanged("formatter");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And the Window Containing the Chart:
private void AddNewSeries(List<double> lstPayments, string sSeriesName, SolidColorBrush colorBrush)
{
if (m_SeriesCollection == null)
{
m_SeriesCollection = new SeriesCollection
{ new ColumnSeries
{
Values = new ChartValues<double>(lstPayments),
Title = sSeriesName,
Fill = colorBrush
}
};
}
else
{
m_SeriesCollection.Add(
new ColumnSeries
{
Values = new ChartValues<double>(lstPayments),
Title = sSeriesName,
Fill = colorBrush
}
);
}
}
private void UpdateChartsBasedOnDates(Chart chart, string sChartTitle)
{ //Pass data to the chart
chart.chartData.SeriesCollection = m_SeriesCollection;
chart.chartData.Labels = GetFormattedDates(m_lstCombinedDates);
chart.chartData.Title = sChartTitle;
}
This all works perfectly when I just allow the default tooltip to be displayed on the chart. I am hung up on the fact that the chart data is supplied by a SeriesCollection, but the custom ToolTip, in the example on the LiveCharts website https://lvcharts.net/App/examples/v1/wpf/Tooltips%20and%20Legends doesn't use the SeriesCollection. Some of the charts displayed by my custom chart control have more than 1 series. I tried binding the Tooltip control to the SeriesCollection to get the data values, but that didn't work. Granted I may have done it incorrectly. Obviously the ToolTip XAML I am showing here is from the example code on the LiveCharts website, but I don't know where to go from here.
Is it possible to use make the tooltip use the series collection? What would be the best way for me to either use the Series Collection, or the ChartData class, or can I use
public TooltipData Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged("Data");
}
}
from the ToolTip C# code to display the data in the ToolTip?
I'm so confused.
Edit: I forgot to mention what my goal is. I want to display a tooltip that says
string.Format("{0} Payment: ${1}", SeriesName, PaymentAmount);
This would display Rent Payment: $1000. or MortgagePayment: $1000.00
Without being a deep LiveCharts Tooltip Wiz I did manage to get a version going that worked for me. My chart is also using a SeriesCollection, so maybe this can be helpful for you.
This is how I integrated it into my chart:
CartesianChart cartesianChart = ...;
_cartesianChart.DataTooltip = new CustomTooltip
{
SelectionMode = TooltipSelectionMode.OnlySender
};
This is my CustomToolTip class (I believe this is pretty much straight out of the documentation somewhere):
public partial class CustomTooltip : UserControl, IChartTooltip
{
private TooltipData _data;
public CustomTooltip()
{
InitializeComponent();
//LiveCharts will inject the tooltip data in the Data property
//your job is only to display this data as required
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public TooltipData Data
{
get => _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));
}
}
public partial class CustomTooltip : UserControl, IChartTooltip, INotifyPropertyChanged
{
private bool _contentLoaded;
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent()
{
if (_contentLoaded)
return;
System.Uri resourceLocater = new System.Uri(
"/Plotting/CustomTooltipDesign.xaml",
System.UriKind.Relative
);
System.Windows.Application.LoadComponent(this, resourceLocater);
_contentLoaded = true;
}
}
Then I built a XAML component, just like you. First super simple:
public partial class CustomTooltipDesign : UserControl
{
public CustomTooltipDesign()
{
DataContext = this;
}
}
Then I had a little more effort to build the actual tooltip:
<UserControl x:Class="expomrf4utility.CustomTooltipDesign"
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:expomrf4utility="clr-namespace:ExpoMRF4Utility"
xmlns:wpf="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance expomrf4utility:CustomTooltip}"
x:Name="Control" d:DesignWidth="1" d:DesignHeight="1">
<UserControl.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{Binding Foreground}"></Setter>
</Style>
<wpf:SharedConverter x:Key="SharedConverter"/>
<wpf:SharedVisibilityConverter x:Key="SharedVisibilityConverter"/>
<wpf:ChartPointLabelConverter x:Key="ChartPointLabelConverter"/>
<wpf:ParticipationVisibilityConverter x:Key="ParticipationVisibilityConverter"/>
<BooleanToVisibilityConverter x:Key="Bvc"></BooleanToVisibilityConverter>
<CollectionViewSource x:Key="GroupedPoints" Source="{Binding Data.Points}"/>
</UserControl.Resources>
<UserControl.Template>
<ControlTemplate>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" >
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Source={StaticResource GroupedPoints}}" Grid.IsSharedSizeScope="True">
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type wpf:DataPointViewModel}">
<Grid Margin="2" VerticalAlignment="Center" HorizontalAlignment="Center" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" SharedSizeGroup="Title"/>
<RowDefinition Height="Auto" SharedSizeGroup="Label"/>
<RowDefinition Height="Auto" SharedSizeGroup="Label"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="1" Text="{Binding Series.Title}" HorizontalAlignment="Left" FontWeight="Bold"/>
<TextBlock Grid.Row="2" Text="{Binding ChartPoint.Y, StringFormat='Value: {0} V/m'}" HorizontalAlignment="Left"/>
<TextBlock Grid.Row="3" Text="{Binding ChartPoint, Converter={StaticResource ChartPointLabelConverter}, StringFormat='Datetime: {0}'}" HorizontalAlignment="Left"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</ControlTemplate>
</UserControl.Template>
Note how I had to create bindings for the different properties that I wanted to display. To that end I slightly cheated when creating the data sets themselves. I used GLineSeries but I have a feeling it should work with other series as well:
int bandIndex = ...;
GearedValues<double> gearedValues = (some double[] data).AsGearedValues();
series.Add(new GLineSeries
{
Values = gearedValues,
LabelPoint = chartPoint => loggerData.Labels[(int)chartPoint.X].ToString(),
Tag = bandIndex,
Title = bandIndex.ToString()
});
Note that I chartPoint.X is the (zero-based) index of the the specific point (in X) - whereas chartPoint.Y is the Y-value of the point (also used in the XAML). Using this index I can look up in my list of names loggerData.Labels and return the timestamp associated with this index.
I am teaching myself... I cannot understand why the UI won't update when a second class is involved. I am missing something basic and I don't get it.
In the first Class:
I have two ObservableCollections bound to two WPF ListViews, which is bound correctly and works.
I have a Command bound to a Button to move items from one Collection to the other, which works as expected.
In the second Class (backcode) I have implemented "Drag and Drop". On Drop I try to call the same Method (which is in the first Class and is used by the Button/Command. The Command is also in the first class).
On "Drag and Drop" the items are moved from one collection to the other (confirmed with Console.Writeline), however the UI doesn't update like it does with the Button/Command.
I believe the problem is that with "Drag and Drop" I am calling the Method from another class. I thought I could do that, but I must not be doing it right?
I have included everything from 4 files (xaml, backcode, class, relayCommand) so hopefully it is easy to reproduce. Can anyone tell me why & how to get this to work???
<Window x:Class="MultipleClassDragAndDrop.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:MultipleClassDragAndDrop"
xmlns:ViewModel="clr-namespace:MultipleClassDragAndDrop.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="716" Width="500">
<Window.Resources>
<ViewModel:MultiColumnViewModel x:Key="MultiColumnViewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Mode=OneWay, Source={StaticResource MultiColumnViewModel}}" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<Button Content="Test Command" Command="{Binding Test_Command}"/>
</StackPanel>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView1" Background="Black" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView1, UpdateSourceTrigger=PropertyChanged}" MouseMove="ListView1_MouseMove" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightPink" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<Grid Grid.Row="0" Grid.Column="2" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView2" Background="Black" MinHeight="300" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView2, UpdateSourceTrigger=PropertyChanged}"
MouseMove="ListView1_MouseMove"
AllowDrop="True" Drop="ListView2_Drop" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightBlue" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
BackCode
using MultipleClassDragAndDrop.ViewModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
namespace MultipleClassDragAndDrop
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
int lb_itemIndex = ListView1.SelectedIndex;
// Package the data.
DataObject data = new DataObject();
data.SetData("Int", lb_itemIndex);
data.SetData("Object", this);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
private void ListView2_Drop(object sender, DragEventArgs e)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()}");
base.OnDrop(e);
int index = (int)e.Data.GetData("Int");
// Call A Method In A Different Class
objMultiColumnViewModel.AddAndRemove(index);
e.Handled = true;
}
}
}
My ViewModel Class
using MultipleClassDragAndDrop.ViewModel.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Input;
namespace MultipleClassDragAndDrop.ViewModel
{
public class ActiveJob : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
public string _JobID;
public string JobID
{
get { return _JobID; }
set
{ _JobID = value; NotifyPropertyChanged("JobID"); }
}
public string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set
{ _CustomerName = value; NotifyPropertyChanged("CustomerName"); }
}
}
public partial class MultiColumnViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
//Test Command
private ICommand _Test_Command;
public ICommand Test_Command
{
get
{
if (_Test_Command == null)
{
_Test_Command = new RelayCommand<object>(ExecuteTest_Command, CanExecuteTest_Command);
}
return _Test_Command;
}
}
public bool CanExecuteTest_Command(object parameter)
{
return true;
}
public void ExecuteTest_Command(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
AddAndRemove(0);
Mouse.OverrideCursor = Cursors.Arrow;
}
public void AddAndRemove(int selectedIndex)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()} Index = {selectedIndex}\n");
ActiveJobListView2.Add(ActiveJobListView1[selectedIndex]);
ActiveJobListView1.RemoveAt(selectedIndex);
foreach (var item in ActiveJobListView1)
{
System.Console.WriteLine($"ActiveJobListView1: {item.JobID}, {item.CustomerName}");
}
System.Console.WriteLine($" ");
foreach (var item in ActiveJobListView2)
{
System.Console.WriteLine($"ActiveJobListView2: {item.JobID}, {item.CustomerName}");
}
}
public MultiColumnViewModel()
{
ActiveJobListView1 = new ObservableCollection<ActiveJob>();
ActiveJobListView2 = new ObservableCollection<ActiveJob>();
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB100", CustomerName = "Smith" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB101", CustomerName = "Jones" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB102", CustomerName = "Black" });
}
#region Properties
private ObservableCollection<ActiveJob> _ActiveJobListView1;
public ObservableCollection<ActiveJob> ActiveJobListView1
{
get { return _ActiveJobListView1; }
set
{
_ActiveJobListView1 = value;
NotifyPropertyChanged("ActiveJobListView1");
}
}
private ObservableCollection<ActiveJob> _ActiveJobListView2;
public ObservableCollection<ActiveJob> ActiveJobListView2
{
get { return _ActiveJobListView2; }
set
{
_ActiveJobListView2 = value;
NotifyPropertyChanged("ActiveJobListView2");
}
}
#endregion
}
}
When binding to a Collection, there are 3 kinds of ChangeNotification you need:
The Notification that informs the UI if something was added or removed from the Collection. That is the only kind of Notification ObservableCollection provides.
The Notification on the property exposing the ObservableCollection. Due to case 1 binding and the lack of a "add range", it is a bad idea to do bulk-modifications of a exposed List. Usually you create a new list and only then Expose it to the UI. In your case those would be the properties "ActiveJobListView1" and it's kind.
The Notification on every property of every type exposed in the collection. That would be "ActiveJob" in your case.
Some of those are often forgotten, with Case 2 being the most common case. I wrote a small introduction into WPF and the MVVM pattern a few years back. maybe it can help you here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf
You have issues with different instances of the same class.
Change:
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
To:
var objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
and it should work
EDIT:
What you are doing is strongly against MVVM principals.
EDIT-2
I had to do some modification to you code to make it work:
In your XAML:
<Window.DataContext>
<local:MultiColumnViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
In your MainWindow.cs:
public MainWindow()
{
InitializeComponent();
objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
}
private MultiColumnViewModel objMultiColumnViewModel;
I have the Contact class that implements the INotifyPropertyChanged:
public class Contact : INotifyPropertyChanged
{
public Contact(Contact contact)
{
this.Username = contact.Username;
this.GUID = contact.GUID;
this.Msg = contact.Msg;
this.Ring = contact.Ring;
}
private string username;
public string Username
{
get { return username; }
set
{
username = value;
NotifyPropertyChanged(nameof(Username));
}
}
public Guid GUID { get; set; }
public bool Msg { get; set; }
private bool ring;
public bool Ring
{
get { return ring; }
set
{
ring = value;
NotifyPropertyChanged(nameof(Ring));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This is the main page:
public sealed partial class MainPage : Page
{
public ObservableCollection<Contact> Contacts = new ObservableCollection<Contact>();
public MainPage()
{
this.InitializeComponent();
Contacts.Add(new Contact("Contact001", Guid.NewGuid(), false, false));
Contacts.Add(new Contact("Contact002", Guid.NewGuid(), false, false));
}
private void AddContactButton_Click(object sender, RoutedEventArgs e)
{
Contacts.Add(new Contact("ContactN", Guid.NewGuid(), false, false));
}
private void ContactsListView_ItemClick(object sender, ItemClickEventArgs e)
{
Contact clickedContact = (Contact)e.ClickedItem;
int index = Contacts.IndexOf(clickedContact);
Contacts.ElementAt(index).Username = "Qwerty";
}
}
}
This is the XAML:
<Page
x:Class="ContactsListBinding.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ContactsListBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:data="using:ContactsListBinding.Models"
xmlns:namespace="ContactsListBinding.Models">
<Page.Resources>
<data:MessageToImageConverter x:Key="MessageToImageConverter" />
<data:RingToImageConverter x:Key="RingToImageConverter" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0">
<Button Name="AddContactButton" Content="Add Contact" Click="AddContactButton_Click" />
<CheckBox Name="MessageMeCheckBox" Content="Message me" />
<CheckBox Name="DeleteMeCheckBox" Content="Delete me" />
</StackPanel>
<ListView Grid.Row="1" Grid.Column="0" Name="ContactsListView"
IsItemClickEnabled="True"
ItemClick="ContactsListView_ItemClick"
ItemsSource="{x:Bind Contacts}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:Contact">
<Grid Width="500">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{x:Bind Username}" VerticalAlignment="Center" />
<Image Grid.Row="0" Grid.Column="1" Width="15" Height="15" Source="{x:Bind Msg, Converter={StaticResource MessageToImageConverter}}" />
<Image Grid.Row="0" Grid.Column="2" Width="15" Height="15" Source="{x:Bind Ring, Converter={StaticResource RingToImageConverter}}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
So, when I click on an item, I change it's Ring property to true. I have debugged and it is changing to true. The only problem is that my UI isn't updating. Any ideas why?
Okay guys, I finally got it to work. It seems that the Binding Mode should be set to OneWay, instead of the default OneTime. For example the correct xaml should be:
<TextBlock Grid.Row="0" Grid.Column="0" Text="{x:Bind Username, Mode=OneWay}" VerticalAlignment="Center" />
Thank you very much for all your help! :)
I have a list of buildings. Buildings have their own class (core), and are saved in an ObservableCollection. Building are displayed in the list, but when I change a variable which is visible in the list, that variable doesn't change in xaml.
Here is the source of class:
public class core
{
// core ----------------------------------------------
static public ObservableCollection<core> cores { get; set; } = new ObservableCollection<core>();
string namef = "building";
public core()
{
cores.Add(this);
}
public string Namef
{
get { return namef; }
set { namef = value; }
}
}
In wpf - xaml:
<Page x:Class="idle.pages.game"
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:idle.pages"
mc:Ignorable="d"
d:DesignHeight="454.259" d:DesignWidth="757.012"
Title="game">
<Grid Margin="0" x:Name="gridx">
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TabControl x:Name="tabControl">
<TabItem Header="Budovy">
<Grid Background="#FFE5E5E5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox x:Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10,7" HorizontalAlignment="Stretch" Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Column="0" Grid.Row="0" Grid.RowSpan="3" CornerRadius="2"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Namef}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Frame x:Name="frame" Content="Frame" Grid.Column="1" NavigationUIVisibility="Hidden" Source="/idle;component/pages/Building.xaml"/>
</Grid>
</TabItem>
</TabControl>
</Grid>
and in wpf - c#:
public partial class game : Page
{
public game()
{
InitializeComponent();
new core() { Namef = "b1"};
new core() { Namef = "b2"};
new core() { Namef = "b3"};
core.start(this);
listBox.ItemsSource = xxx;
}
public ObservableCollection<core> xxx { get; set; }
}
Im sure, that the variable inside of the class is being changed, but xaml not. What's wrong?
You need to implement the INotifyPropertyChanged interface and invoke the PropertyChanged event in the setters of all properties that can change (and you want to have updated in the UI). If for example you want to see changes to Namef, you'd have to implement it like this:
public class core : INotifyPropertyChanged
{
static public ObservableCollection<core> cores { get; set; }
= new ObservableCollection<core>();
string namef = "building";
public core()
{
cores.Add(this);
}
public string Namef
{
get { return namef; }
set
{
if(namef == value) return;
namef = value;
OnPropertyChanged("Namef");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName)
{
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I am working with WPF MVP. I have a UserControl with DataBinding.
After the user interaction (click event) the property in the presenter is null, but at the next modify on the text when the binding is happening, the customer class not null. I think there is two reference. The datacontext of the view is equal with the presenter.
The datacontext of the view is the presenter.
public class AddCustomerPresenter : PresenterBase<AddCustomerView>
{
private Customer customer;
public Customer Customer
{
get {
return customer ?? new Customer(); }
set
{
customer = value;
RaisePropertyChanged(PropertyName(() => this.Customer));
}
}
/// <summary>
/// todo: a view-khoz lehetne irni egy factoryt
/// </summary>
public AddCustomerPresenter()
{
base.View = new AddCustomerView { DataContext = this};
View.Save += View_Save;
}
void View_Save(object sender, EventArgs e)
{
int a = 2;
}
public void AddToCustomers()
{
new UnitOfWork().CustomerRepository.Add(customer);
}
}
public partial class AddCustomerView : UserControl
{
public event EventHandler Save;
public AddCustomerPresenter Presenter { get { return (AddCustomerPresenter)DataContext; } }
public AddCustomerView()
{
InitializeComponent();
}
private void Save_Click(object sender, RoutedEventArgs e)
{
var handler = Save;
if (handler != null) handler(this, EventArgs.Empty);
}
}
public class Customer : Notifier
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChanged(PropertyName(() => this.Name));
}
}
Address address;
public Address Address
{
get { return address??new Address(); }
set
{
address = value;
RaisePropertyChanged(PropertyName(() => this.Address));
}
}
string phoneNumber;
public string PhoneNumber
{
get { return phoneNumber; }
set
{
phoneNumber = value;
RaisePropertyChanged(PropertyName(() => this.Address));
}
}
}
<UserControl x:Class="RentACar.Views.AddCustomerView"
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">
<Border BorderBrush="Green" BorderThickness="2">
<DockPanel HorizontalAlignment="Center">
<Label HorizontalAlignment="Center"
Content="asdf"
DockPanel.Dock="Top"
FontSize="34" />
<Grid DataContext="{Binding Customer}" DockPanel.Dock="Top">
<Grid.Resources>
<Style TargetType="Label">
<Setter Property="FontSize" Value="12" />
</Style>
<Style TargetType="TextBox">
<Setter Property="Width" Value="112" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<Style TargetType="RowDefinition">
<Setter Property="Height" Value="auto" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Content=":" />
<TextBox x:Name="asdf"
Grid.Column="1"
Text="{Binding Name}" />
<GroupBox Grid.Row="2"
Grid.ColumnSpan="2"
Header="">
<Grid DataContext="{Binding Address}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Content=":" />
<TextBox Grid.Column="1" Text="{Binding City}" />
<Label Grid.Row="1" Content=":" />
<TextBox Grid.Row="1"
Grid.Column="1"
Text="{Binding Street}" />
<Label Grid.Row="2" Content=":" />
<TextBox Grid.Row="2"
Grid.Column="1"
Text="{Binding StreetNumber}" />
</Grid>
</GroupBox>
<Label Grid.Row="3" Content=":" />
<TextBox Grid.Row="3"
Grid.Column="1"
Text="{Binding PhoneNumber}" />
</Grid>
<Button Width="auto"
Margin="0 10 10 10"
HorizontalAlignment="Right"
Click="Save_Click"
Content="" />
</DockPanel>
</Border>
</UserControl>
Demostation:
Problem is in Customer property getter.
return customer ?? new Customer();
it means:
if(customer != null)
{
return customer;
}
else
{
return new Customer();
}
Untill you set customer field you will get new Customer(); every time.
but you probably want something like this.
if(customer != null)
{
return customer;
}
else
{
customer = new Customer();
return customer;
}
or you can simply set that field :) for example in constructor of AddCustomerPresenter and then it's not necessary to have that getter complicated.
it could be:
public Customer Customer
{
get
{
return customer;
}
set
{
customer = value;
RaisePropertyChanged(PropertyName(() => this.Customer));
}
}