У меня есть диаграмма внутри пользовательского элемента управления (Chart U C), которая похожа на пример масштабирования и панорамирования LiveCharts, поэтому Chart U C имеет привязки к своему коду. У меня есть другой пользовательский элемент управления (назовем его Item U C), который содержит 2 UC Chart. Элемент U C имеет привязки и команды, все из которых работают с его моделью представления (Элемент VM). То, что я не смог выяснить, как установить соединение между свойством Серии UC Chart и ВМ Item.
Диаграмма U C 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>
Диаграмма U C Код позади:
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();
}
}
}
Элемент U C 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>
Элемент представления модели U C:
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;
}
}
}
Главное окно 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>
Код основного окна позади:
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);
}
}
}
Модель вида вкладки Contorl:
namespace responsive_ui_test.View_Models
{
class TabControlViewModel : ViewModelBase
{
public ObservableCollection<DeviceViewModel> Items { get; } = new ObservableCollection<DeviceViewModel>();
}
}
Что мне нужно иметь внутри DeviceViewModel
находится свойство SeriesCollection диаграммы для каждой диаграммы внутри элемента U C. Мне просто не понятно, как получить эту связь в моей ситуации. Я потратил много времени на попытки, и все работает, кроме графиков. Я очень ценю любую помощь, заранее большое спасибо!