我正在尝试模拟 NFL 驱动器图表以可视化游戏中的一些驱动器数据。可以在这里看到我大致试图模仿的一个例子,由 nfl.com 提供。我正在努力实现以下目标:
- 显示比赛场地
- 在比赛场地顶部绘制数据
- 当我将鼠标悬停在某些东西上时,我想触发一个事件(例如 ToolTip)
http://www.nfl.com/gamecenter/2011100909/2011/REG5/jets@patriots#menu=drivechart
这是我现在所处的位置,首先是 xaml:
<StackPanel x:Name="myStackPanel" Orientation="Vertical">
<StackPanel.Background>
<ImageBrush ImageSource="{StaticResource PlayingField}" Stretch="Fill" />
</StackPanel.Background>
<ItemsControl Background="AliceBlue" Opacity="0.5"
ItemsSource="{Binding Path=DriveDetails}"
ItemTemplate="{StaticResource myDataTemplate}"
ItemsPanel="{StaticResource myItemsPanelTemplate}" />
</StackPanel>
结果是:
(对不起,由于我是新手,所以无法添加截图!)
我会尽力描述我所看到的,我相信你们中的大多数人在阅读 xaml 后都会明白这一点。基本上,我在背景中看到一个足球场,在左上角 - 我的 ItemsControl 控件。
StackPanel 是不言自明的,我现在将在调整图像大小时遇到问题,因为一码不代表一个像素,但我将把它留到另一天,我主要关心的是如何可视化数据。此外,驱动器可视化不应从左上角开始:D 所以纯粹处理 ItemsControl 我只剩下这个:
(对不起,由于我是新手,所以无法添加截图!)
再一次,我将尝试描述图像。它是前面提到的 ItemsControl 控件的特写,没有背景(足球场)图像。
注意: ItemsControl 背景已设置为 AliceBlue,以便更容易看到,一旦我开始工作,它将是透明的。
ItemsSource 绑定到“Play”类的列表:
public class Play
{
public int Quarter { get; set; }
public int Result { get; set; }
public PlayType TypeOfPlay { get; set; }
public double WidthOfPlay { get; set; }
}
PlayType 是一个枚举:
public enum PlayType
{
Run,
Pass,
Penalty,
Punt,
FieldGoal
}
该列表获得了一些数据……(示例数据,我知道已经获得了第一个倒数,哈哈)
List<Play> SamplePlays = new List<Play>()
{
new Play { Quarter = 1, Result = 5, TypeOfPlay = PlayType.Penalty, WidthOfPlay = 30 },
new Play { Quarter = 1, Result = 5, TypeOfPlay = PlayType.Run, WidthOfPlay = 30 },
new Play { Quarter = 1, Result = 5, TypeOfPlay = PlayType.Pass, WidthOfPlay = 30 },
new Play { Quarter = 1, Result = 40, TypeOfPlay = PlayType.Punt, WidthOfPlay = 240 }
};
…并在传递参数类的帮助下传递给 ViewModel…</p>
ucHomeInstance = ucDriveChartInstance;
((ucDriveChart)ucHomeInstance).setViewModel(new ucDriveChartVM(new ucDriveChartTP()
{ Plays = SamplePlays }));
… 分配给 DriveDetails 属性的位置
public class ucDriveChartVM : ViewModelBase
{
public List<Play> DriveDetails { get; set; }
public ucDriveChartVM(ucDriveChartTP transferParameter)
{
this.DriveDetails = transferParameter.Plays;
}
}
这是 ItemsPanel 的 xaml:
<ItemsPanelTemplate x:Key="myItemsPanelTemplate">
<StackPanel Orientation="Horizontal" IsItemsHost="True" />
</ItemsPanelTemplate>
这是 ItemTemplate 的 xaml - 请注意,我绑定到 WidthOfPlay,因为背景图形上的一码大约是 6 个单位,一旦我整理好图形并调整大小,然后我将绑定到实际的 Result 属性:
<DataTemplate x:Key="myDataTemplate">
<StackPanel Orientation="Horizontal" Width="{Binding WidthOfPlay}"
SnapsToDevicePixels="True" Style="{StaticResource onMouseOver}">
<Border Padding="0,5,0,5">
<Grid>
<StackPanel Style="{StaticResource onMouseOver}">
<Rectangle Width="{Binding WidthOfPlay}" Height="10"
Fill="{Binding TypeOfPlay,
Converter={StaticResource PlayTypeToColourConverter}}" />
</StackPanel>
</Grid>
</Border>
</StackPanel>
</DataTemplate>
我正在将 PlayType 枚举转换为 Brush,这就是为什么使用示例数据可以看到显示的颜色的原因。
<converter:PlayToColour x:Key="PlayTypeToColourConverter"
ColourRun="Red" ColourPass="Blue" ColourPenalty="Yellow" ColourSpecial="Purple" />
所以所有这一切都工作正常 - 但显然表示并不如所愿 - 查看顶部的示例,一个驱动器由多个播放组成,它们彼此相邻放置,但作为一个播放可能会导致负码数,它们也被放置在彼此下方,在最后一场比赛结束时抵消!
所以我想这是我的问题 - 我怎样才能以这样的形式表示数据,驱动器的每次播放都位于该特定驱动器的前一个播放下方,但也从最后一个驱动器的末尾开始(以某种方式偏移? )
由于游戏包含多个驱动器,我将在稍后阶段传递一个“驱动器”列表,其中包括驱动器起始位置和该驱动器的播放列表(而不是本文中的简单播放列表例子)。
这意味着整个事物也需要是可滚动的!
我认为我在使用 ItemsControl 控件以及 ItemTemplate 和 DataTemplate 时走在正确的轨道上,但我可以肯定地使用一些专家建议来实现这一点。
如果您已经读到这里,感谢您抽出宝贵的时间 - GO PATS ;)
[编辑]
多亏了AngelWPF(虚拟化)和Rachel(一切!),我实际上设法让它工作,至少对于我提供 ItemsControl 的播放列表。瑞秋,你的博客很棒。AttachedProperties 条目确实非常有用,感谢您分享您的知识!
在稍后的某个阶段,我希望能够添加一两个屏幕截图,以更好地展示我在胡扯的内容;)
正如建议的那样,我使用(以及其他属性) PlayIndex 修改了 Play 类以进行网格定位
public class Play
{
public int PlayIndex { get; set; }
public int Quarter { get; set; }
public int Down { get; set; }
public int YardsToGo { get; set; }
public int Position { get; set; }
public PlayType TypeOfPlay { get; set; }
public PlayDetail DetailsOfPlay { get; set; }
public PlayResult ResultOfPlay { get; set; }
public int YardsOfPlay { get; set; }
public double PlayLength { get; set; }
public string Player1 { get; set; }
public string Player2 { get; set; }
}
并使用该 PlayIndex,将 ItemContainerStyle 添加到 ItemsControl
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding PlayIndex}" />
<Setter Property="Grid.Row" Value="{Binding PlayIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
将 ItemsPanelTemplate 从 StackPanel 更改为 Grid 对此也有帮助 :) 现在每个 Play 都以各自的颜色彼此相邻以及在彼此下方显示。将 ToolTip 添加到 ItemsControl(使用 Play 类中的其他属性)将其打磨掉。
正如我已经说过的,这现在非常适合播放项目列表(为简单起见而缩短):
List<Play> SamplePlays = new List<Play>()
{
new Play { PlayIndex = 0, Position = 30, YardsOfPlay = 5, PlayLength = 25 },
new Play { PlayIndex = 1, Position = 35, YardsOfPlay = 15, PlayLength = 75 },
new Play { PlayIndex = 2, Position = 50, YardsOfPlay = 0, PlayLength = 0 },
new Play { PlayIndex = 3, Position = 50, YardsOfPlay = 8, PlayLength = 40 },
new Play { PlayIndex = 4, Position = 58, YardsOfPlay = 1, PlayLength = 5 },
new Play { PlayIndex = 5, Position = 59, YardsOfPlay = 30, PlayLength = 150 }
};
但是,我将如何绑定我在原始帖子末尾写的关于传递“驱动器”列表的内容?
请注意 - 我在原帖中写了这篇文章,我希望这“允许”我用更多信息编辑这篇文章,而不是问一个全新的问题!
给定以下“驱动器”类:
public class Drive
{
public int DriveIndex { get; set; }
public int StartPos { get; set; }
public int EndPos { get; set; }
public double DriveLength { get; set; }
public DriveResult Result { get; set; }
public List<Play> DrivePlays { get; set; }
}
用数据填充列表(使用与上面相同的播放)
List<Drive> SampleDrives = new List<Drive>()
{
new Drive { DriveIndex = 0, StartPos = 30, EndPos = 49, DriveLength = 19,
DrivePlays = new List<Play>()
{
// create plays here
}},
new Drive { DriveIndex = 1, StartPos = 30, EndPos = 49, DriveLength = 19,
DrivePlays = new List<Play>()
{
// create plays here
}}
};
ViewModel 现在看起来像这样:
public class ucDriveChartVM : ViewModelBase
{
public List<Play> DriveDetails { get; set; }
public List<Drive> Drives { get; set; }
public ucDriveChartVM(ucDriveChartTP transferParameter)
{
this.DriveDetails = transferParameter.Plays;
this.Drives = transferParameter.Drives;
}
}
我显然需要为这个 ItemsControl(包含驱动器)提供另一个 DataTemplate,但是这个 DataTemplate 必须在该驱动器列表中保存播放。我很快发现你不能将内容放在矩形中,所以我不得不考虑一下:/
在进行了一些实验之后,我绑定到 DriveLength 属性以图形方式查看给定驱动器的长度,并提出了一个 DataGridCell,以便我可以在其中放置内容(例如 StackPanel)。
(我知道除了驱动器的长度之外我没有绑定任何东西,这个例子中的 Rectangle 只是为了在我测试时显示一些东西!)
<DataTemplate x:Key="myDataTemplateDrives">
<StackPanel Orientation="Horizontal" Width="{Binding DriveLength}"
SnapsToDevicePixels="True" Margin="0,0,1,0">
<Border>
<Grid>
<StackPanel>
<DataGridCell>
<StackPanel Width="{Binding Path=DriveLength}">
<Rectangle Height="10" Fill="Gray" />
</StackPanel>
</DataGridCell>
</StackPanel>
</Grid>
</Border>
</StackPanel>
</DataTemplate>
但是我已经喝完第四杯咖啡了,我在绑定到 List 对象中的 List 时遇到问题?Drives.DrivePlays 之类的东西?!
所以我从一个简单的开始(谢谢你们)
List<Play>
绑定,到
List<Drive>
绑定,其中包含一个
List<Play>!
称为“DrivePlays”:D
我是否遗漏了一些明显的东西或者我需要更多的咖啡?
编辑
我已经在一个单独的问题中解释了问题的最后一部分