【问题标题】:WPF are there preferred ways to do 2D charting without a third party library?WPF 是否有在没有第三方库的情况下进行 2D 图表制作的首选方法?
【发布时间】:2017-03-17 06:37:27
【问题描述】:

我想做一个项目,为几乎无形的图表制作一个非常通用的可重用用户控件。我希望在制图方面有所作为,但似乎地球上几乎每个人都免费使用 WPF 工具包或 Sparrow Charts 以第 3 方方式制作图表。有没有人有绑定或构建完全独立的图表制作方式的经验?我正在考虑做一些通用的事情,比如在画布上绑定多段线并通过。我很好奇是否有其他人曾走这条路,并且有关于为事件引发和潜在依赖属性设置绑定的提示。我正在考虑遵循 MVVM 架构方法并完成大部分与 ViewModel 的绑定,但我最终希望公开属性以进行更新。

在概念上与此类似(UserControl 嵌入另一个 View 或 MainForm):

<StackPanel>
    <Label x:Name="lblCustomDataGridHeader" Content="{Binding TestText}"  HorizontalAlignment="Center" FontSize="24"/>
    <Canvas Height="260" Width="300">
      <Polyline Points="{Binding Points}" Stroke="LightBlue" StrokeThickness="4" />
    </Canvas>
  </StackPanel>

ViewModel 属性:

public ViewModel()
{
    TestText = "Line Chart";
    //Obviously some converter or something else here to accept one or many   lines
    Points = "0,260 10,250 20,245 40,200 50,250 80, 200, 140,100";
}

public string TestText {
    get { return _testText; }
    set 
    {
        _testText = value;
        OnPropertyChanged(NameOf(TestText));
    }
}

private string _points;
public string Points {
    get { return _points; }
    set 
    {
        _points = value;
        OnPropertyChanged(NameOf(Points));
    }
}

稍后编辑

我也尝试过做一个绑定到类的模板化控件

 <Style TargetType="{x:Type local:LineGraph}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type local:LineGraph}">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
          </Grid.RowDefinitions>
          <TextBlock Grid.Row="0" Text="Hello" FontSize="20"/>
          <Border Grid.Row="1" BorderThickness="1" BorderBrush="Black" CornerRadius="15" Margin="10">
            <Canvas Margin="10" x:Name="PART_Canvas">
              <Canvas.LayoutTransform>
                <ScaleTransform ScaleX="1" ScaleY="-1" />
              </Canvas.LayoutTransform>
            </Canvas>
          </Border>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

类(其中一些需要清理,因为我正在使用其他人的实现,它是在 VB.NET 中并转换的):

public class LineGraph : Control, INotifyPropertyChanged
{

    //CONSTRUCTOR
    static LineGraph()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(LineGraph), new FrameworkPropertyMetadata(typeof(LineGraph)));
    }

    public event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged;

    public void OnPropertyChanged(string info)
    {
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }


    public static readonly DependencyProperty _Trends = DependencyProperty.RegisterReadOnly("Trends", typeof(Collection<ChartDataSegment>), typeof(LineGraph), new PropertyMetadata(new Collection<ChartDataSegment>())).DependencyProperty;
    public Collection<ChartDataSegment> Trends {
        get { return (Collection<ChartDataSegment>)GetValue(_Trends); }
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        dynamic canvas = GetTemplateChild("PART_Canvas") as Canvas;
        if (canvas != null && Trends != null) {
            foreach (void trend_loopVariable in Trends) {
                trend = trend_loopVariable;
                DrawTrend(canvas, trend);
            }
        }
    }

    private void DrawTrend(Canvas drawingCanvas, ChartDataSegment Trend)
    {
        dynamic t = Trend as ChartDataSegment;
        if (t != null && t.Points != null) {
            for (int i = 1; i <= t.Points.Count - 1; i++) {
                dynamic toDraw = new Line {
                    X1 = t.Points(i - 1).X,
                    Y1 = t.Points(i - 1).Y,
                    X2 = t.Points(i).X,
                    Y2 = t.Points(i).Y,
                    StrokeThickness = 2,
                    Stroke = t.LineColor
                };
                drawingCanvas.Children.Add(toDraw);
            }
        }
    }

}

public class ChartDataSegment : DependencyObject
{


    public static readonly DependencyProperty _LineColor = DependencyProperty.Register("LineColor", typeof(Brush), typeof(ChartDataSegment), new PropertyMetadata(null));
    public Brush LineColor {
        get { return (Brush)GetValue(_LineColor); }
        set { SetValue(_LineColor, value); }
    }

    public static readonly DependencyProperty _LineThickness = DependencyProperty.Register("LineThickness", typeof(Thickness), typeof(ChartDataSegment), new PropertyMetadata(null));
    public Thickness PointThickness {
        get { return (Thickness)GetValue(_LineThickness); }
        set { SetValue(_LineThickness, value); }
    }

    public static readonly DependencyProperty _Points = DependencyProperty.Register("Points", typeof(ObservableCollection<Point>), typeof(ChartDataSegment), new UIPropertyMetadata(null));
    public ObservableCollection<Point> Points {
        get { return (ObservableCollection<Point>)GetValue(_Points); }
        set { SetValue(_Points, value); }
    }
}

以及在 ViewModel 中的实现:

var lineTrend1 = new ChartDataSegment {
    LineColor = Brushes.Blue,
    Points = new ObservableCollection<Point>({
        new Point {
            X = 1,
            Y = 1
        },
        new Point {
            X = 50,
            Y = 20
        },
        new Point {
            X = 100,
            Y = 100
        },
        new Point {
            X = 150,
            Y = 130
        }
    })
};

var ctrl = new LineGraph();
ctrl.Trends.Add(lineTrend1);

我最担心的不是这可以实现,而是按需注入项目,不仅仅是在实例化时,而是在对象已经运行之后,以便根据需要不断更新行。 EG:异步调用来更新折线图,而不是托管需要处理然后重新调用的静态图表。

由于 Stack Overflow 想要具体问题,我的直接问题是:“您能否轻松注入集合(点)的集合(行)并使用具有依赖属性的 Canvas 进行自我更新?”

【问题讨论】:

  • 这真的不是主题。真的很难回答是/否的问题。如果您从未做过任何 UI 工作,那很难。因此,除非您想至少花几个月的时间在上面,否则只需获得一个 3rd 方库。它们很昂贵,原因与离婚很昂贵一样。
  • 因此:“由于 Stack Overflow 需要问题的具体细节,我的直接问题是:“您能否轻松注入集合(点)的集合(行)并使用具有依赖属性的 Canvas 进行自我更新? ” “是不是具体的?哪一部分不够具体?
  • 这不是势利,它只是有标准。我在底部读到了,但是如果我添加一个“是”或“否”的答案对你有什么好处?你怎么知道我是对的?见鬼,我可以根据我的经验告诉你,按照你的要求去做很容易,但这只是触及控制设计的表面。但话又说回来,也许我只是很烂,你会发现它很容易。这就是您的问题不在主题范围内的原因。它的粗体部分不会使这不正确。

标签: c# wpf mvvm charts


【解决方案1】:

是的,您可以创建一个图表控件,一个带有一种“图表”的简单控件,当您想要扩展那段代码时,真正的工作就开始了,然后您必须记住,在某些时候您的需求可能会发生变化并且你开始的设计将不再好。我个人认为这将是一个很好的练习。但是您需要记住,您将收到的数据会发生变化,没有人愿意传递Points 的集合,我个人希望传递整数或浮点数或小数的集合。几乎所有让我喜欢的东西。在这一点上,您将开始挠头并考虑再次实现所有这些逻辑的另一种方法。那是我那几个月后的朋友。我并不是要阻止您接受这项任务,但您的出发点在不久的将来肯定会改变。
顺便说一句,听威尔他知道他在说什么。

【讨论】:

  • 除了:“编码很难,请使用第三方。”如果我可以制作一个图表并绑定到它(我的图表工作得很好,我在上面给出了)我只需要更多的步骤来查看是否可以通过 API 或其他方式轻松地进行自我更新。我知道这是一个有趣的问题,但这就是我问的原因。 SO 变得有点让我不安:“太模糊了!!!您正在询问有关架构选择和技术以及 2 + 2 = ?”的问题。如果我知道该怎么做,我就不会在这里了。我刚开始只是对自我更新感到好奇。
  • 我也不知道它是什么,但这种“太模糊”的狂热正在增加,以至于任何接近于讨论的程度:“我正在这样做并且正在考虑这个,你怎么看?”正在被拒绝。我从其他用户离开或说出来时注意到它。
  • 我觉得很有趣的 SO 指南是:'程序员常用的软件工具;并且是软件开发所特有的一个实用的、可回答的问题”。我的问题:您能否轻松注入集合(点)的集合(行)并使用具有依赖属性的 Canvas 进行自我更新?” 看起来很简单。显然没有像这个问题那样明确定义:stackoverflow.com/questions/332365/…
  • 真的,我的答案是在这里提供我的方法的观点。正如我所说,我并不是在劝阻你放弃这个想法。顺便说一句,UserControls 通常没有附加 ViewModel,所有逻辑都在后面的代码中完成,would you like to populate a ViewModel of a ListView? 不,你不会,我也不会。保持UserControl 自包含可能使用DependencyProperties,因此您可以在使用该控件时使用绑定。
  • 老实说,我问的原因之一是我不明白为什么使用依赖属性而不是直接绑定或转换器更好。在我看来,我读过的代码更多,你可以用它做更多的事情,但我并没有采取一种或另一种方式。通常我会在 ViewModel 中的 API 服务或数据库中执行所有业务逻辑和数据,并在后面的代码中专门处理动画和其他事情。恕我直言,如果您将代码隐藏到您的 UI 模式中,则更难测试。
【解决方案2】:

我制作了我需要的特定类型的图表,和你一样,我没有使用任何第三方库。我一直在寻找它,我发现了这个项目项目: http://www.codeproject.com/Articles/446888/Custom-Spider-or-Radar-Chart-Control

他们制作了一个类似于以下的图表:

源代码可供下载,并且不使用第三方代码,因此您可以轻松查看它们所做的一切。

创建图表只需要做:

<WpfCharts:SpiderChart Title="Spider chart"  
                   Lines="{Binding Lines}" 
                   Axis="{Binding Axes}" 
                   Minimum="0" 
                   Maximum="1" 
                   Ticks="5" 
                   ShowLegend="True" 
                   LegendLocation="BottomRight" 
                   LegendBackgroundColor="Aquamarine"/> 

如您所见,它已经绑定了属性。我已经在 MVVM 项目中使用它了。

我希望它能引导你找到你正在寻找的东西。

【讨论】:

  • 这正是我想要的。谢谢!他们做了我在第二个例子中想到的类似的事情。设置样式,然后触发依赖项。而且它是自包含的图书馆,甚至更好。
  • 我很高兴有帮助。
猜你喜欢
  • 2015-01-23
  • 2010-10-26
  • 2015-04-22
  • 1970-01-01
  • 2012-05-04
  • 1970-01-01
  • 2019-10-14
  • 2021-10-27
  • 1970-01-01
相关资源
最近更新 更多