Связывание данных серии LiveCharts между пользовательским элементом управления и моделью представления - PullRequest
0 голосов
/ 10 января 2020

У меня есть диаграмма внутри пользовательского элемента управления (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. Мне просто не понятно, как получить эту связь в моей ситуации. Я потратил много времени на попытки, и все работает, кроме графиков. Я очень ценю любую помощь, заранее большое спасибо!

1 Ответ

0 голосов
/ 16 января 2020

Наконец я нашел решение. Я только что создал 2 разных пользовательских элемента управления LiveCharts, которые одинаковы, но Binding для SeriesCollection диаграммы назван по-разному. Таким образом, я могу привязать SeriesCollection обеих диаграмм к модели представления, и она отлично работает.

...