我自定义的一个控件,使用这个控件的模块需要在子节点中设置一些对象及其属性
比如一个曲线图,有左右两个纵坐标,我希望做好之后,在使用这个控件的 xaml 中会这样写
<s:MyChartLine>
<s:MyChartLine.LeftAxis MaxValue="1000" MinValue="-200" Background="Red" />
<s:MyChartLine.RightAxis MaxValue="200" MinValue="-200" Background="Blue" />
</s:MyChartLine>
纵坐标是一个类:
public class YAxis {
public double MaxValue{ get; set;}
public double MinValue{ get; set; }
public Brush Background { get; set; }
}
应该要怎么做?
//ChartControl Memory Leak using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; using System.Windows.Threading; using L.Bussiness.Services; using L.Bussiness.画布图元s.读显s.Parts.Charts; using L.Bussiness.设置面板s; using L.CommonCtrls; using L.CommonCtrls.Converters; using L.LModules.Utils; using L.LModules.ViewUtils; using LiveCharts; using LiveCharts.Defaults; using LiveCharts.Definitions.Series; using LiveCharts.Wpf; using LiveCharts.Wpf.Charts.Base; using PropertyChanged; using PropertyTools.DataAnnotations; using PropertyTools.Wpf; namespace L.Bussiness.画布图元s.读显s { public class Primitive图表读取器3 : PrimitivePathBindBase { public class ChartLineBean : IMetric { public string DevAddress { get; set; } [Filed()] [AutoUpdateText] [System.ComponentModel.Category("曲线|基本设置")] [System.ComponentModel.DisplayName("颜色设置")] public Color Color { get; set; } = Colors.Transparent; [Filed()] [AutoUpdateText] [System.ComponentModel.Category("曲线|基本设置")] [System.ComponentModel.DisplayName("曲率0-1")] public bool LineSmoothness { get; set; } [Filed()] public string Path { get; set; } [Filed()] public RwModel RwModel { get; set; } public IConvertible Value { get; set; } public ISeriesView SeriesView { get; set; } } private DispatcherTimer _dispatcherTimerInterval; private DispatcherTimer _dispatcherTimerChart; [Filed] [DoNotCheckEquality] [AutoUpdateText] [SortIndex(10)] [HeaderPlacement(HeaderPlacement.Above)] [CtrlType(CtrlCategory.ChartList)] [Height(160)] [System.ComponentModel.Category("图表相关|基本")] [System.ComponentModel.DisplayName("绑定列表")] [System.ComponentModel.Description("部分支持多个.")] public override ObservableCollection<IMetric> Metrics { get; } = new ObservableCollection<IMetric>(); [Filed(true)] [DoNotCheckEquality] [AutoUpdateText] [SortIndex(3560)] [System.ComponentModel.Category("图表相关|基本")] [System.ComponentModel.DisplayName("动画效果")] [System.ComponentModel.Description("会影响性能.")] public virtual bool CanAnimations { get => _canAnimations; set { _canAnimations = value; if (ChartCtrl != null) { ChartCtrl.DisableAnimations = (!_canAnimations); } } } private int _tickInterval = 1; [Filed(true)] [DoNotCheckEquality] [AutoUpdateText] [SortIndex(5520)] [System.ComponentModel.Category("图表相关|基本")] [System.ComponentModel.DisplayName("周期(秒)")] [System.ComponentModel.Description("至多点数.")] public int TickInterval { get => _tickInterval; set { if (value < 1 || value > 7200) { this.GetWindowScada()?.MsgWarn("超出范围,范围1-7200"); return; } _tickInterval = value; _dispatcherTimerInterval.Interval = TimeSpan.FromSeconds(_tickInterval); } } private int _maxPointsCount = 5; [Filed] [DoNotCheckEquality] [AutoUpdateText] [SortIndex(5620)] [System.ComponentModel.Category("图表相关|基本")] [System.ComponentModel.DisplayName("至多周期数")] [System.ComponentModel.Description("至多点数.")] public int LimitPoints { get => _maxPointsCount; set { if (value < 3 || value > 200) { this.GetWindowScada()?.MsgWarn("超出范围,范围3-200"); return; } _maxPointsCount = value; } } //private int _xLines = 5; //[Filed(true)] //[DoNotCheckEquality] //[AutoUpdateText] //[SortIndex(5720)] //[System.ComponentModel.Category("图表相关|基本")] //[System.ComponentModel.DisplayName("X栅格数")] //[System.ComponentModel.Description("X栅格数.")] //public int XLines //{ // get => _xLines; // set // { // if (value < 3 || value > 20) // { // this.GetWindowScada()?.Alert("超出范围,范围3-50"); // return; // } // _xLines = value; // if (AxisX?.Separator != null) // { // AxisX.Separator.IsEnabled = true; // AxisX.Separator.Step = TimeSpan.FromSeconds(TickInterval * LimitPoints).Ticks / (double)_xLines; // AxisX.Separator.IsEnabled = false; // } // //AxisX.Separator.Step = TimeSpan.FromSeconds(_maxPointsCount * _tickInterval).Ticks; // } //} //private int _yLines = 5; //[Filed(true)] //[DoNotCheckEquality] //[AutoUpdateText] //[SortIndex(5820)] //[System.ComponentModel.Category("图表相关|基本")] //[System.ComponentModel.DisplayName("Y格线数")] //[System.ComponentModel.Description("Y格线数.")] //public int YLines //{ // get => _yLines; // set // { // if (value < 3 || value > 20) // { // this.GetWindowScada()?.Alert("超出范围,范围3-20"); // return; // } // _yLines = value; // } //} private Color _xColor = ThemeDefine.Default.Text0.ToArgbColor(); [Filed] [DoNotCheckEquality] [AutoUpdateText] [SortIndex(6580)] [System.ComponentModel.Category("图表相关|基本")] [System.ComponentModel.DisplayName("X栅线色")] [System.ComponentModel.Description("背景颜色.")] public virtual Color XColor { get => _xColor; set { _xColor = value; if (AxisX?.Separator != null) { AxisX.Separator.Stroke = new SolidColorBrush(value); } } } private Color _xLabelColor = ThemeDefine.Default.Text0.ToArgbColor(); [Filed] [DoNotCheckEquality] [AutoUpdateText] [SortIndex(6570)] [System.ComponentModel.Category("图表相关|基本")] [System.ComponentModel.DisplayName("X轴字色")] [System.ComponentModel.Description("背景颜色.")] public virtual Color XLabelColor { get => _xLabelColor; set { _xLabelColor = value; if (AxisX != null) AxisX.Foreground = new SolidColorBrush(value); } } private double _xLabelAngle = 0; [Filed] [DoNotCheckEquality] [AutoUpdateText] [Slidable(0, 360)] [SortIndex(6590)] [System.ComponentModel.Category("图表相关|基本")] [System.ComponentModel.DisplayName("X字角度")] [System.ComponentModel.Description("背景颜色.")] public double XLabelAngle { get => _xLabelAngle; set { if (value < 0 || value > 360) { this.GetWindowScada()?.MsgWarn("超出范围,范围3-20"); return; } _xLabelAngle = value; if (AxisX != null) AxisX.LabelsRotation = _xLabelAngle; } } private Color _yColor = ThemeDefine.Default.Text0.ToArgbColor(); [Filed(true)] [AutoUpdateText] [SortIndex(6780)] [System.ComponentModel.Category("图表相关|基本")] [System.ComponentModel.DisplayName("Y栅线色")] [System.ComponentModel.Description("背景颜色.")] public virtual Color YColor { get => _yColor; set { _yColor = value; if (AxisY?.Separator != null) { AxisY.Separator.Stroke = new SolidColorBrush(value); } } } private Color _yLabelColor = ThemeDefine.Default.Text0.ToArgbColor(); [Filed] [DoNotCheckEquality] [AutoUpdateText] [SortIndex(6790)] [System.ComponentModel.Category("图表相关|基本")] [System.ComponentModel.DisplayName("Y轴字色")] [System.ComponentModel.Description("背景颜色.")] public virtual Color YLabelColor { get => _yLabelColor; set { _yLabelColor = value; if (AxisY != null) AxisY.Foreground = new SolidColorBrush(value); } } public CartesianChartEx ChartCtrl { get; } private AxisEx AxisX { get; } private Axis AxisY { get; } private const string __XFORMAT = "hh:mm:ss"; public Primitive图表读取器3() { ChartCtrl = new CartesianChartEx(); //ChartCtrl.DisableAnimations = true; //ChartCtrl.AnimationsSpeed = TimeSpan.FromMilliseconds(999); //LiveCharts.Wpf.Components.ChartUpdater updater = (LiveCharts.Wpf.Components.ChartUpdater)ChartCtrl.Model.Updater; AxisX = new AxisEx() { DisableAnimations = true, LabelsRotation = 0, LabelFormatter = d => new DateTime((long)(d < 0 ? 0 : d)).ToString(__XFORMAT) }; AxisX.ShowLabels = true; AxisX.IsEnabled = false; //AxisX.IsMerged = true; AxisX.Unit = 10.0; AxisX.Separator.StrokeThickness = 1.0; var now = RpcHttp.Current.GetTimeNowByServerBase(); //AxisX.Labels = Enumerable.Range(0, 5).Select(t => now.Add(TimeSpan.FromSeconds(t)).ToString(__XFORMAT)).ToList(); ChartCtrl.AxisX = new AxesCollection() { AxisX }; AxisY = new Axis() { DisableAnimations = true, LabelsRotation = 0, LabelFormatter = d => d.ToString() }; AxisY.Separator.StrokeThickness = 1.0; ChartCtrl.AxisY = new AxesCollection() { AxisY }; ChartCtrl.Series = new SeriesCollection(); WidthEx = 300; HeightEx = 200; CanAnimations = false; CornerRadius = new CornerRadius(0); BackgroundEx = 0xff0b3869.ToArgbColor(); WhenMeticsChanged = WhenMeticsChangedMethod; _dispatcherTimerChart = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(999.0) }; _dispatcherTimerInterval = new DispatcherTimer { Interval = TimeSpan.FromSeconds(_tickInterval) }; Loaded += OnLoaded; Unloaded += OnUnloaded; //Mappers.Xy<DateTimePoint>().X() //var mapper1 = Mappers.Xy<TickPoint>() // .X((value, index) => value.Time.Ticks) // .Y(value => value.Value.GetHashCode()); //LiveCharts.Charting.For<TickPoint>(mapper1, SeriesOrientation.Horizontal); } protected override void OnInitContent(ContentPresenter content) { content.Content = ChartCtrl; LimitPoints = LimitPoints; TickInterval = TickInterval; CanAnimations = CanAnimations; XColor = XColor; //XLines = XLines;//dependence on LimitPoints and TickInterval XLabelColor = XLabelColor; XLabelAngle = XLabelAngle; YColor = YColor; YLabelColor = YLabelColor; } private void OnLoaded(object sender, RoutedEventArgs e) { _dispatcherTimerChart.Tick += DispatcherTimerOnTick; _dispatcherTimerChart.Start(); _dispatcherTimerInterval.Tick += DispatcherTimerIntervalOnTick; _dispatcherTimerInterval.Start(); } private void OnUnloaded(object sender, RoutedEventArgs e) { //var updater = (LiveCharts.Wpf.Components.ChartUpdater)ChartCtrl.Model.Updater; //updater.Timer.Tick -= updater.OnTimerOnTick; //updater.Timer.Stop(); //updater.Timer.IsEnabled = false; //updater.Timer = null; //var updater = ChartCtrl.Model.Updater; //foreach (var seriesView in ChartCtrl.Series) //{ // if (seriesView.Values is IDisposable values) // values.Dispose(); //} _dispatcherTimerChart.Tick -= DispatcherTimerOnTick; _dispatcherTimerChart.Stop(); _dispatcherTimerInterval.Tick -= DispatcherTimerIntervalOnTick; _dispatcherTimerInterval.Stop(); ChartCtrl.Series.ForEach(t => { t.Values.Clear(); t.Values.CollectGarbage(t); }); } readonly Dictionary<string, IConvertible> _pathValueLast = new Dictionary<string, IConvertible>(); private void DispatcherTimerIntervalOnTick(object sender, EventArgs e) { var now = RpcHttp.Current.GetTimeNowByServerBase(); Metrics.ToList().ForEach(t => { var bean = t.AsType<ChartLineBean>(); if (bean?.SeriesView == null) return; if (_pathValueLast.ContainsKey(t.Path)) { var val = _pathValueLast[t.Path]; if (val == null) return; bean.SeriesView.Values?.Add(new DateTimePoint(now, val.ToDouble(null))); } while (bean.SeriesView?.Values?.Count > LimitPoints) bean.SeriesView?.Values?.RemoveAt(0); }); } private void DispatcherTimerOnTick(object sender, EventArgs e) { ChartCtrl.Update(false, true); } private int _indexColorUsage = 0; private bool _canAnimations; private void WhenMeticsChangedMethod(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (IMetric metric in e.NewItems) { var beanMetricAndSeries = metric.AsType<ChartLineBean>(); if (beanMetricAndSeries == null) continue; var series = new LineSeries() { Values = new ChartValues<DateTimePoint>() { new DateTimePoint() }, Title = metric.Path, PointGeometrySize = 4, }; series.DataContext = beanMetricAndSeries; series.Values.Initialize(series); //Enumerable.Range(1,3).ToList().ForEach(t=> series.Values.Add(new DateTimePoint())); series.SetBinding(LineSeries.LineSmoothnessProperty, new Binding(ClassEx.GetPropertyName<ChartLineBean>(t => t.LineSmoothness)) { Converter = new BooleanToDoubleConverter(), Mode = BindingMode.TwoWay }); beanMetricAndSeries.SeriesView = series; if (beanMetricAndSeries.Color == Colors.Transparent) { beanMetricAndSeries.Color = ChartSourceEx.Colors[_indexColorUsage % Chart.Colors.Count]; _indexColorUsage++; } series.SetBinding(LineSeries.StrokeProperty, new Binding(ClassEx.GetPropertyName<ChartLineBean>(t => t.Color)) { Converter = new ColorToBrushConverter(), Mode = BindingMode.TwoWay }); ChartCtrl.Series.Add(series); while (series.Values.Count > 0) series.Values.RemoveAt(0); //ChartCtrl.Update(true, true); } break; case NotifyCollectionChangedAction.Remove: foreach (IMetric metric in e.OldItems) { var beanMetricAndSeries = metric.AsType<ChartLineBean>(); if (beanMetricAndSeries == null) continue; ChartCtrl.Series.Remove(beanMetricAndSeries.SeriesView); beanMetricAndSeries.SeriesView = null; } break; } ChartCtrl.Update(false, true); lock (_pathValueLast) { _pathValueLast.Clear(); Metrics.ToList().ForEach(t => { _pathValueLast.Add(t.Path, t.Value); }); } } protected override void InsertMetric(ObservableCollection<IMetric> metrics, IMetric metric) { //base.InsertMetric(metrics, metric); var metircAndSeries = new ChartLineBean() { Path = metric.Path, RwModel = metric.RwModel, Value = metric.Value }; metrics.Add(metircAndSeries); } public override void OnValueChanged(IPathValue metric) { if (_pathValueLast.ContainsKey(metric.Path)) _pathValueLast[metric.Path] = metric.Value; //var metircAndSeries = Metrics.FirstOrDefault(t => t.Path == metric.Path)?.AsType<Bean>(); //if (metircAndSeries == null) return; //metircAndSeries.SeriesView?.Values?.Add(new DateTimePoint(InternalRestClient.Current.GetTimeNowByServerBase(), metric.Value.ToDouble(null))); //while (metircAndSeries.SeriesView?.Values?.Count > LimitPoints) metircAndSeries.SeriesView?.Values?.RemoveAt(0); } public void Dispose() { ChartCtrl.Series.Clear(); } ~Primitive图表读取器3() { _dispatcherTimerInterval?.Stop(); _dispatcherTimerInterval = null; _dispatcherTimerChart?.Stop(); _dispatcherTimerChart = null; } } }
该控件有内存泄漏问题,上述代码可用,但已未用。
其实控件我已经写好并测试通过了,功能基本满足需求
但是在使用这个控件时,放到窗体的 xaml 不方便,不能象WPF自带的控件那样可以嵌套写信息
比如我定义了两个纵坐标,想设置不同的属性,我希望调用时可以这样写:
<c:MyChartLine x:Name="chartLine1">
<LeftAxis Color="Black" MaxValue="900" MinValue="-100" />
<rightAxis Color="Blue" MaxValue="100" MinValue="-50" />
</c:MyChartLine>
但现在不知道怎么做成这种嵌套设置,所以只能这样写
<c:MyChartLine x:Name="chartLine1" MaxLeftAxis="900" MinLeftAxis="-100" LeftAxisColor="Black" ...... />
@血狼一族: 写个派生类就搞定了
@花飘水流兮:
什么派生类
@血狼一族:
class CartesianChartEx:CartesianChart { 需要什么属性 自己加; }
您老人家根本没弄明白我想问的是啥
@血狼一族: 貌似看反了~;
不过道理是一样的,至于实现可能需要些更多而已。
class AxisEx:Axis{需要什么属性 自己加;}
如果Chart才是他控件的参数,顶多再自行设置Axis.MaxValue时,去设置Chart的对应属性。
再次说一下,该控件内存泄漏,作者已经不维护,消耗比较大。
推荐 xaml只做基础视图结构,不要做业务逻辑设置;
如绑定推荐:series.SetBinding(LineSeries.StrokeProperty, new Binding(ClassEx.GetPropertyName<ChartLineBean>(t => t.Color)) { Converter = new ColorToBrushConverter(), Mode = BindingMode.TwoWay });
这样可以编译时报错,Bean变更也不至于不能智能替换。
@花飘水流兮:
我强调一下,我不是想知道怎么做图表控件,我的图表控件已经做好了并且应用到项目里去了。控件是完全自己一点点写出来的,基本上满足项目需要。我拿图表控件说事,只是举个例子说明我想知道的方法而已。
但是由于一堆参数要设置,象左右两个纵坐标轴都要设置最大值、最小值、刻度大小、颜色等,还有横坐标轴也有一堆信息要设置,现在在调用控件时,我只能写成类似:
<local:ChartLine LeftYAxis_MaxValue="100" LeftYAxis_MinValue="-100" LeftYAxis_Scale="20" RightYAxis_MaxValue="200" RightYAxis_MinValue="-100" RightYAxis_Scale="30" XAxis_Range="1000" />
这样写 xaml 很 low,并且维护也不方便,代码也不好阅读
我希望在窗体中的 xaml 在调用控件时,写成这种结构化的形式:
local:ChartLine
<LeftYAxis MinValue="-100" MaxValue="100" Scale="20" />
<RighttYAxis MinValue="-100" MaxValue="200" Scale="30" />
<XAxis Range="1000" />
</local:ChartLine>
这才是我想知道的
我的项目里可能还有很多控件要做,我不想都写成前面的那各自形式,而是后面这种结构化的形式
在自定义控件中,设置依赖属性,在自定义控件的xaml中,绑定其后台的依赖属性,其语法为:
<lvc:Axis MaxValue="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=Max }"/> ,其中的Max就是自定义的依赖属性。在使用控件时,定义一个具有自动通知功能的属性,绑到自定义控件的自定义属性上,就可以。希望对你有所启发。
你好
你说的 xaml 中绑定依赖属性应该放在哪个元素里?
我这里的自定义控件全部是放在 Grid 里的, Axis 只是简单的C#类
@血狼一族: 需要对外暴露的属性,都需要定义为依赖属性。定义依赖属性,有固定的格式,定义在控件的cs文件中。编译以后,在主窗体的xaml文件中,引用它,此时,就能直接使用你定义的依赖属性(也即你想要对外暴露的属性)。
@HangPuWind:
你说的定义成依赖属性,是不是 DependencyProperty 那些操作?这些属性我都定义过了的。
在我的工程里,即使不定义成依赖属性,如果属性是个简单类型,也可以被暴露给调用的一方。比如
public class MyControl : UserControl {
public int MaxValue { get; set; }
}
这样在调用时,在我的VS里是可以这样写的:
<local:MyControl MaxValue="10"></local:MyControl>
但是我定义的那个依赖属性的类型本身又是一个类类型,单纯地在 xml 元素的属性里是设置不完的
public class Axis {
public double MaxValue{ get; set;}
public double MinValue { get; set; }
// 其它复杂类型
}
public MyControl : UserControl {
public static readonly DependencyProperty XAxisProperty = DependencyProperty.Register("XAxis", typeof(Axis), typeof(MyControl));
public Axis XAxis {
get{
return GetValue(XAxisProperty) as Axis;
}
set{
SetValue(AxisProperty, value);
}
}
这样,在调用时,我希望能够写成:
local:MyControl
<XAxis MaxValue="100" MinValue="-100" />
</local:MyControl>
因为,在我的这个控件里,可能还会有更多的象 Axis 这样的类类型的属性,甚至更复杂,比如 Axis 里还有属性是类类型的
@血狼一族: 可以试试附加属性。