【问题标题】:How to draw on Canvas widgets embedded inside a ListView如何在嵌入在 ListView 中的 Canvas 小部件上绘图
【发布时间】:2011-12-05 11:34:29
【问题描述】:

这是一个非常基本的要求,但我被卡住了!对于 WPF/.Net - 我只想在我的 ListView 中动态绘制到 Canvas 列中。一次失败的尝试:

<ListView name="myGridView">
    <GridViewColumn Header="ColumnA" DisplayMemberBinding="{Binding Path=ColumnA}" />
    <GridViewColumn DisplayMemberBinding="{Binding Path=ColumnB}">
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SUSPECT!
        <Canvas Name="canvasColumn" Width="100" Height="20" />
    </GridViewColumn>
</GridView>

在我的代码中,我有一个类“MyData”,其字段绑定到非画布 ListView 列。我遍历 ListView 中的一些“Whatever”创建项目:

foreach (Whatever whatever in whatevers)
{
    MyData myData = new MyData();
    myData.ColumnA = whatever.A;

    myData.ColumnB = new Canvas();
    Line line = new Line();
    line.Stroke = System.Windows.Media.Brushes.Black;
    line.X1 = line.Y1 = 1;
    line.X2 = line.Y2 = 100;
    line.StrokeThickness = 1;
    myData.ColumnB.Children.Add(line);

    myListView.Items.Add(myData);
}

这不起作用:屏幕画布列中的每一行都显示文本“System.Windows.Controls.Canvas”。这并不奇怪 - 我已经以与文本列相同的方式绑定列,并且类型名的一些 toString 转换似乎开始了。但是,我尝试了其他一些方法,但无法显示 Canvas。

我还尝试删除上面标记为“SUSPECT”的列绑定和 myData 的 ColumnB 字段,寻求一种通过列表视图引用画布小部件的方法,即某种形式:

myListView.reference-to-new-row-and-canvas-column = theNewCanvasIDrewOn;

我的一些搜索发现了样式、ItemPanel 配置等丑陋的混乱:请 - 如果有必要,我至少希望它可以保持最小......

非常感谢任何指导。

干杯, 托尼

更新

出于我的目的,最小的解决方案似乎是将 DataTemplate 添加到 App.xaml 的 Application.Resources 标记:

<DataTemplate x:Key="myTemplate">
    <Canvas Width="60" Height="20" Background="Red" ClipToBounds="True" >
        <ContentPresenter Content="{Binding myCanvasField}" />
    </Canvas>
</DataTemplate>

并将 GridViewColumn 定义为:

<GridViewColumn CellTemplate="{StaticResource myTemplate}" Header="title" />

感谢 Dean 为我指明了正确的方向,感谢 Binding to Canvas 了解特定于画布的详细信息。然后,我“绘制”添加到 ListView 的对象的 Canvas 属性成员。

【问题讨论】:

  • 你有什么样的图纸?
  • @Dmitry:列表视图将显示“任务”,图片将以对数刻度显示开始日期、承诺交货日期、预期交货日期等条形 - 基本上一目了然检查任务的进展情况。稍后我可能会变得更喜欢并想指出假期/休假期或其他什么......
  • 顺便说一句:如果您正在谈论“在”画布上“绘图”,那么您并没有真正“获得”WPF。 WPF 使用保留模式图形,这与 WinForms 的绘制事件有很大不同;而 WPF 的 Canvas 是一个布局面板,而不是绘图表面。您不会“在”画布上“绘图”;您向其中添加控件/元素。如果您希望在表面上“绘图”,请查看 DrawingGroup 或 WriteableBitmap。
  • @Joe:我理解区别,但最终 - 相同的差异;-)。画布能够显示我需要的东西......干杯。

标签: c# .net wpf listview wpf-controls


【解决方案1】:

【讨论】:

  • 我认为这里更大的问题是如何让您的画布与您的业务对象对话。
  • @Dean:感谢您的提示。我更改为 &lt;GridViewColumn Header="..." CellTemplate="{StaticResource ct}" /&gt; 并在 App.xaml 中添加了 &lt;DataTemplate x:Key="ct"&gt; &lt;Canvas ... &gt; &lt;ContentPresenter Content="{Binding CanvasProperty}" /&gt; &lt;/Canvas&gt; &lt;/DataTemplate&gt;。将其附加到问题中以便于参考。一切都好:-)。
【解决方案2】:

您可以将 TaskVisualizer 隐含为自定义控件,然后将其托管在列表模板中。这会将您的任务可视化代码与您的全局 UI 代码分开。这样做的好处是可以轻松地在其他地方重用任务可视化 - 例如,当您将鼠标悬停在其他视图中的任务上时,您可以轻松地在工具提示中显示相同的图形。

【讨论】:

  • 嗨 Ricibob,感谢您的建议 - 从概念上讲,它听起来确实很干净 - 但由于我目前对如何做到这一点没有任何见解,因此我采用了一种更简单的方法,尽管是耦合的方法。有一天我可能会尝试将其作为学习练习。谢谢,托尼
  • @TonyDelroy:是的,与 WPF 的所有东西一样,在整理自定义控件方面有相当长的正常运行时间 - 但如果您有时间投资,它会有所回报!如果您对这条路线感兴趣,我发现 Sams - WPF Control Development Unleashed 是一个很好的起点。
【解决方案3】:

这是我的看法。这个想法是使用迷你 DSL 在画布和业务对象之间交换信息。

XAML:

<Window x:Class="DrawInCanvas.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DrawInCanvas"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid x:Name="g">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Id" Binding="{Binding Item1}" />
                <DataGridTemplateColumn Header="Bar">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Canvas HorizontalAlignment="Left" 
                                    Height="20"
                                local:CanvasDrawing.Drawing="{Binding Item2}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace DrawInCanvas
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // just a sample
            Dictionary<int, string> barDefinitions = new Dictionary<int, string>(3)
            {
                { 1, "100$red" },
                { 2, "220$yellow" },
                { 3, "40$blue" }
            };

            this.g.ItemsSource =
                Enumerable.Range(1, 3).Select(t => 
                    new Tuple<int, string>(t, barDefinitions[t]));
        }
    }

    public class CanvasDrawing : DependencyObject
    {
        public static readonly DependencyProperty DrawingProperty =
            DependencyProperty.RegisterAttached("Drawing",
                typeof(string),
                typeof(CanvasDrawing),
                new PropertyMetadata(new PropertyChangedCallback((o, e) => 
                {
                    CanvasDrawing.Draw((Canvas)o, (string)e.NewValue);
                })));

        public static void SetDrawing(Canvas canvas, string drawing)
        {
            canvas.SetValue(CanvasDrawing.DrawingProperty, drawing);
        }

        public static string GetDrawing(Canvas canvas)
        {
            return (string)canvas.GetValue(CanvasDrawing.DrawingProperty);
        }

        private static void Draw(Canvas canvas, string drawing)
        {
            string[] parts = drawing.Split("$".ToCharArray());

            canvas.Width = double.Parse(parts[0]);
            canvas.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString(parts[1]));
        }
    }
}

【讨论】:

  • 嗨 Dmitry,感谢您的建议和代码。我不太确定如何使用它,因为它似乎放弃了 ListView 并采用了 DataGrid?我什至不清楚如何在画布上添加一条线,或者如何创建合适的“字符串绘图”值。如果/当我了解更多 .NET 时,我可以返回一些东西。谢谢,托尼
  • 嗨,Tony,最简单的方法就是 Dean 的回答。
猜你喜欢
  • 2011-06-18
  • 2018-08-10
  • 1970-01-01
  • 1970-01-01
  • 2017-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-09
相关资源
最近更新 更多