在scrollviewer画布上确定用户单击的集合

时间:2012-07-19 13:12:45

标签: c# wpf xaml scrollviewer

我正在尝试使用基于MVVM的WPF应用程序进行开发的时间轴。

我的愿望是能够分辨用户希望查看的“bin”,因为通常会有比View中显示的事件更多的事件。这基本上是一个小的小堆栈面板,它在代码隐藏中开发并显示在用户选择的bin上方。更多细节如下。

基本背景是:

  • 我正在记录ViewModel中发生的事件,我将它们称为事件。

  • 我在时间轴上的视图中将它们显示为50x50画布(每个事件一个)。这里的细微差别在于我的空间非常有限,所以我将显示的事件的实际数量限制为3个堆叠稍微偏移(只是想象卡片堆叠在彼此后面,卡片的顶部和右部在下面显示)每个时间块的彼此。

  • 在每个时间刻度(每30秒),画布向左滚动75个像素,当然所有绘制的元素也随之移动。这也为事件设置了“箱子”。基本上0到29.9秒之间的所有内容都在bin 0中,30 - 59.9是bin 1,依此类推。

  • 我正在使用一个PreviewMouseLeftButtonDown事件,该事件链接到负责显示绑定事件的ItemsControl。

现在一切正常。我可以收集鼠标点击,我一直在尝试使用一些基本的数学来确定点击了哪组事件。这是基于点击时鼠标的X位置并考虑到ScrollViewer窗口的任何滚动。不幸的是,我没有得到一个正确的“垃圾桶”。

我尝试过的其他事情:

  • 在canvas元素中添加一个标记,其中包含有关它所属的bin的信息。这不起作用,因为我得到的代码中的ItemsSource包含每个事件,而不仅仅是那些被点击的事件。这就是我访问ItemsSource的方式。

    private void ItemsControl_PreviewMouseLeftButtonDown( object sender, MouseButtonEventArds e)
    {
        var control = (ItemsControl)sender;
        var isource = control.ItemsSource;
        Debug.Assert(isource != null);
    }
    
  • 我已尝试将其嵌入到stackpanel中,然后再将其放入itemscontrol中。当我有一个嵌套列表但是在我离开列表列表之后它就崩溃了。我不确定此时它是否也是一个可行的选择。

我所拥有的XAML(对于那些可能想要看到它的人)如下:

感兴趣的主要ItemsControl是最后一个,因为它包含用于在ScrollViewer中的画布上绘制事件的datatemplate。另外两个仅用于我在时间线底部使用的时间戳。

<UserControl 
    x:Class="Removed.Views.TransitionTimeline"
    x:ClassModifier="internal"
    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"
    mc:Ignorable="d" 
    d:DesignHeight="300" d:DesignWidth="300">
  <UserControl.Resources>
    <Style x:Key="ISTCanvasStyle" TargetType="Canvas">
      <Setter Property="Background" Value="Transparent" />
    </Style>

    <Style x:Key="ISTBorderStyle" TargetType="Border">
      <Setter Property="BorderThickness" Value="3" />
      <Setter Property="BorderBrush" Value="{Binding ColorBrush}" />
      <Setter Property="CornerRadius" Value="8" />
      <Setter Property="HorizontalAlignment" Value="Center" />
      <Setter Property="VerticalAlignment" Value="Center" />
      <Setter Property="Background" Value="#FF555555" />
      <Setter Property="Height" Value="50" />
      <Setter Property="Width" Value="50" />
    </Style>

    <Style x:Key="ISTTextBlockStyle" TargetType="TextBlock">
      <Setter Property="Text" Value="{Binding ShortName}" />
      <Setter Property="HorizontalAlignment" Value="Center" />
      <Setter Property="VerticalAlignment" Value="Center" />
      <Setter Property="FontWeight" Value="Bold" />
      <Setter Property="FontSize" Value="40" />
      <Setter Property="Foreground" Value="White" />
      <Setter Property="TextAlignment" Value="Center" />
    </Style>

    <Style x:Key="EventCountTextStyle" TargetType="TextBlock">
      <Setter Property="Text" Value="{Binding ExtraEvents}" />
      <Setter Property="Canvas.Top" Value="-15" />
      <Setter Property="Canvas.Left" Value="-25" />
      <Setter Property="FontWeight" Value="Bold" />
      <Setter Property="FontSize" Value="13" />
      <Setter Property="Foreground" Value="{Binding EventTextColorBrush}" />
      <Setter Property="TextAlignment" Value="Center" />
    </Style>

    <DataTemplate x:Key="IndividualStateTransitions">
      <Canvas Margin="{Binding Margin}" Style="{ StaticResource ISTCanvasStyle}" >
        <Border Canvas.Left="-12.5" Style="{ StaticResource ISTBorderStyle}" >
          <TextBlock Style="{StaticResource ISTTextBlockStyle}" />
        </Border>
        <TextBlock Style="{StaticResource EventCountTextStyle}" />
      </Canvas>
    </DataTemplate>

    <DataTemplate x:Key="IST">
      <ItemsControl
          ItemsSource="{Binding}" 
          ItemTemplate="{StaticResource IndividualStateTransitions}"
          PreviewMouseLeftButtonDown="ItemsControl_PreviewMouseLeftButtonDown"
          Width="75">
        <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
            <Canvas />
          </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
          <Style TargetType="ContentPresenter">
            <Setter Property="Canvas.ZIndex" Value="{Binding ZIndex}" />
          </Style>
        </ItemsControl.ItemContainerStyle>
      </ItemsControl>
    </DataTemplate>

    <DataTemplate x:Key="BottomTimeBar">
      <Canvas>
        <Line X1="{Binding DashX}" X2="{Binding DashX}" Y1="100" Y2="0" Stroke="#FF646464" StrokeThickness="1" StrokeDashArray="5" StrokeDashCap="Round" />
        <TextBlock Canvas.ZIndex="-999"  Width="50" TextAlignment="Center" Text="{Binding TimerText}" Canvas.Left="{Binding BlockLeft}" 
                       Canvas.Top="85" Foreground="White" Background="#FF444444" FontSize="13" />
      </Canvas>
    </DataTemplate>

  </UserControl.Resources>
  <Grid Name="TimelineGrid" Height="192">
    <Grid.RowDefinitions>
      <RowDefinition Height="92" />
      <RowDefinition Height="100" />
    </Grid.RowDefinitions>

    <Canvas Grid.Row="0" Width="1920" Height="100">
      <ScrollViewer            
          Width="1920"
          Height="100"
          Name="_timelineScrollViewer2" 
          CanContentScroll="True" 
          Background="Transparent"
          HorizontalScrollBarVisibility="Hidden" 
          VerticalScrollBarVisibility="Hidden">
        <Canvas Width="9999" Name="_timelineCanvas2">
        </Canvas>
      </ScrollViewer>
    </Canvas>

    <Canvas Grid.Row="1" Name="MainCanvas" Width="1920" Height="100" >
      <Line X1="0" X2="1920" Y1="0" Y2="0" Stroke="#FFD0D0D0" StrokeThickness="3">
        <Line.Effect>
          <DropShadowEffect BlurRadius="3" ShadowDepth="3" />
        </Line.Effect>
      </Line>
      <Line X1="0" X2="1920" Y1="55" Y2="55" Stroke="#FFD0D0D0" StrokeThickness="3">
        <Line.Effect>
          <DropShadowEffect BlurRadius="3" ShadowDepth="3" />
        </Line.Effect>
      </Line>
      <ScrollViewer            
          Width="1920"
          Height="100"
          Name="_timelineScrollViewer" 
          CanContentScroll="True" 
          Background="Transparent"
          HorizontalScrollBarVisibility="Hidden" 
          VerticalScrollBarVisibility="Hidden"
          PreviewMouseLeftButtonDown="TimelineScrollViewerLeftMouseDown" 
          PreviewMouseLeftButtonUp="LeftMouseUp" 
          PreviewMouseMove="TimelineScrollViewerMouseMove"
          PreviewMouseWheel="TimelineScrollViewerPreviewMouseWheel"
          ScrollChanged="OnTimelineScrollChanged" ClipToBounds="False">
        <Canvas Width="9999" Name="_timelineCanvas">
          <Line Canvas.ZIndex="-1000" Name="CurrentTimeLine" X1="1280" X2="1280" Y1="0" Y2="100" Stroke="#FFD0D0D0" StrokeThickness="3">
            <Line.Effect>
              <DropShadowEffect BlurRadius="3" ShadowDepth="3" />
            </Line.Effect>
          </Line>
          <ItemsControl ItemsSource="{Binding BottomTimeBarData}" ItemTemplate="{StaticResource BottomTimeBar}" Canvas.Left="{Binding LeftScroll}">
            <ItemsControl.ItemsPanel>
              <ItemsPanelTemplate>
                <Canvas />
              </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
          </ItemsControl>
          <ItemsControl ItemsSource="{Binding BottomTimeBarDataPast}" ItemTemplate="{StaticResource BottomTimeBar}" Canvas.Left="{Binding LeftScroll}">
            <ItemsControl.ItemsPanel>
              <ItemsPanelTemplate>
                <Canvas />
              </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
          </ItemsControl>

          <ItemsControl ItemsSource="{Binding DisplayObject}" ItemTemplate="{StaticResource IndividualStateTransitions}" Canvas.Left="{Binding LeftScroll}" Canvas.Top="15" Margin="0.0, 25.5"
                        PreviewMouseLeftButtonDown="ItemsControl_PreviewMouseLeftButtonDown" >
            <ItemsControl.ItemsPanel>
              <ItemsPanelTemplate>
                <StackPanel />
              </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemContainerStyle>
              <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.ZIndex" Value="{Binding ZIndex}" />
              </Style>
            </ItemsControl.ItemContainerStyle>
          </ItemsControl>
        </Canvas>
      </ScrollViewer>
    </Canvas>
  </Grid>
</UserControl>

如果您有任何疑问,请告诉我。我非常乐意澄清发生了什么。最终我正在寻找的是一种确定用户希望查看的“bin”的方法。

更新1:有关绘制对象的其他信息。

我已经定义了自己的内部类,所以我可以将它用作我的ObservableCollection的数据类型,这是在这种情况下绑定的内容。

internal class DisplayObjects : INotifyPropertyChanged
{
    private int _elementbin;
    public int ElementBin
    {
        get { return _elementbin; }
        set
        {
            _elementbin = value;

            if ( PropertyChanged != null )
                PropertyChanged( this, new PropertyChangedEventArgs( "ElementBin" ) );
        }
    }

    private string _shortName;
    public string ShortName
    {
        get { return _shortName; }
        set
        {
            _shortName = value;

            if ( PropertyChanged != null )
                PropertyChanged( this, new PropertyChangedEventArgs( "ShortName" ) );
        }
    }

    private string _margin;
    public string Margin
    {
        get { return _margin; }
        set
        {
            _margin = value;

            if ( PropertyChanged != null )
                PropertyChanged( this, new PropertyChangedEventArgs( "Margin" ) );
        }
    }

    private string _extraevents;
    public string ExtraEvents
    {
        get { return _extraevents; }
        set
        {
            _extraevents = value;

            if ( PropertyChanged != null )
                PropertyChanged( this, new PropertyChangedEventArgs( "ExtraEvents" ) );
        }
    }

    public Color EventTextColor { get; set; }

    public SolidColorBrush EventTextColorBrush
    {
        get
        {
            return new SolidColorBrush( EventTextColor );
        }
    }

    private int _zindex;
    public int ZIndex
    {
        get { return _zindex; }
        set
        {
            _zindex = value;

            if ( PropertyChanged != null )
                PropertyChanged( this, new PropertyChangedEventArgs( "ZIndex" ) );
        }
    }

    public Color Color { get; set; }

    public SolidColorBrush ColorBrush
    {
        get { return new SolidColorBrush( Color ); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

我订阅了一个事件聚合器,因此我可以在发送事件时捕获这些事件。这会触发我的OnEventReceived方法。您需要了解的有关此方法的所有信息是它从收到的事件中获取信息,并根据事件发生的时间确定应该包含哪个bin。这会触发对DrawObject方法的调用。

如果你想尝试模仿这个,你可以用一些硬编码的东西替换stvm的用法。我无法提供实际数据。 stvm.Color只是一种表示事件类型的颜色。 stvm.ShortName只是一个字母。 _bins只是一个List<Int>,用于跟踪相应bin中发生的事件总数。 position只是它所属的bin中的数字事件。因此,如果这是第一个将成为1的事件。如果它是第十个事件,它将是10。

此处的FindLastVisualChild方法仅用于返回给定bin的第3个DisplayObject的位置索引。这允许我更新标记“更多+ + X”。堆栈中的事件。

private void DrawObject( EventViewModel stvm, int position, int bin )
{
    var margin = "";
    var zindex = 0;
    var left = bin * 75.0;
    var textColor = Colors.Transparent;
    var extraEvents = "+ ";
    switch ( position )
    {
        case 1:
            left += 12.5;
            margin = left + ", -5";
            zindex = 3;
            DisplayObject.Add( new DisplayObjects
            {
                Color = stvm.Color,
                ShortName = stvm.ShortName,
                Margin = margin,
                ZIndex = zindex,
                EventTextColor = textColor,
                ExtraEvents = extraEvents,
                ElementBin = bin
            } );
            break;
        case 2:
            left += 22.5;
            margin = left + ", -15";
            zindex = 2;
            DisplayObject.Add( new DisplayObjects
            {
                Color = stvm.Color,
                ShortName = stvm.ShortName,
                Margin = margin,
                ZIndex = zindex,
                EventTextColor = textColor,
                ExtraEvents = extraEvents,
                ElementBin = bin
            } );
            break;
        case 3:
            left += 32.5;
            margin = left + ", -25";
            zindex = 1;
            DisplayObject.Add( new DisplayObjects 
            { 
                Color = stvm.Color, 
                ShortName = stvm.ShortName, 
                Margin = margin, 
                ZIndex = zindex, 
                EventTextColor = textColor, 
                ExtraEvents = extraEvents,
                ElementBin = bin
            } );
            break;
        default:
            //left += 32.5;
            //margin = left + ", -25";
            //DisplayObject.Add( new DisplayObjects { Color = stvm.Color, ShortName = stvm.ShortName, Margin = margin, ZIndex = zindex, EventTextColor = textColor, ExtraEvents = extraEvents } );
            extraEvents += ( _bins[bin] - 3 ) + " more.";
            var test = FindLastVisualChild( bin );
            DisplayObject[test].EventTextColor = Colors.White;
            DisplayObject[FindLastVisualChild( bin )].ExtraEvents = extraEvents;
            break;
    }
}

private int FindLastVisualChild( int bin )
{
    var sum = 0;
    for ( var idx = 0; idx <= bin; idx++ )
        if ( _bins[idx] <= 3 )
            sum += _bins[idx];
        else
            sum += 3;
    return ( sum - 1 );
}

2 个答案:

答案 0 :(得分:1)

您是否尝试过使用e.OriginalSource

我显然无法重现您的项目,但据我所知,您应该能够通过使用此单击来获取Canvas:

var canvas = e.OriginalSource as Canvas

然后你应该能够轻松获得相应的项目,因为我理解它是画布的DataContext:

var item = canvas.DataContext as MyItemViewModel;

但正如我所写,这是猜测。您应该为我们发布更多信息,以便在您的应用中看到更清晰的信息,例如您将项目添加到集合中的代码。

答案 1 :(得分:0)

非常感谢David和Rachel在这里。他们的投入使我得到了答案,我终于开始工作了。

就像参考此后出现并阅读它的人一样。这就是我所做的。

在我在Canvas上IndividualStateTransistions添加的Tag = "{Binding ElementBin}"的数据模板中。然后我添加了bin元素,该元素属于将事件过滤到bin中的代码。

根据Rachel的建议,我将鼠标点击事件移动到此Canvas。

在后面的代码中,我改编了大卫建议的内容:

在OnMouseClickEvent方法中......

var control = (Canvas)sender;
var item = control.DataContext as DisplayObjects;
var itembin = item.ElementBin;

这给了我那个事件组所属的bin,并允许我继续下一步。

再次感谢你们(和女士们)!