I have created slightly customized vertical scrollbar for my DataGrid. In it I have added an ItemsControl to hold positions of the selected items. Here is a mockup so far with hard-coded markers.
Below is my customized vertical scrollbar template where the ItemsControl is placed with hard-coded marker values.
<ControlTemplate x:Key="VertScrollBar" TargetType="{x:Type ScrollBar}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="18" />
<RowDefinition Height="0.00001*" />
<RowDefinition MaxHeight="18" />
</Grid.RowDefinitions>
<Border Grid.RowSpan="3" CornerRadius="2" Background="#F0F0F0" />
<RepeatButton Grid.Row="0" Style="{StaticResource ScrollBarLineButton}" Height="18" Command="ScrollBar.LineUpCommand" Content="M 0 4 L 8 4 L 4 0 Z" />
<Track x:Name="PART_Track" Grid.Row="1" IsDirectionReversed="true">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageUpCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollBarThumb}" Margin="1,0,1,0">
<Thumb.BorderBrush>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource BorderLightColor}" Offset="0.0" />
<GradientStop Color="{DynamicResource BorderDarkColor}" Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Thumb.BorderBrush>
<Thumb.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource ControlLightColor}" Offset="0.0" />
<GradientStop Color="{DynamicResource ControlMediumColor}" Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Thumb.Background>
</Thumb>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageDownCommand" />
</Track.IncreaseRepeatButton>
</Track>
<!-- BEGIN -->
<ItemsControl Grid.Column="0" VerticalAlignment="Stretch" Name="ItemsSelected">
<sys:Double>30</sys:Double>
<sys:Double>70</sys:Double>
<sys:Double>120</sys:Double>
<sys:Double>170</sys:Double>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="SlateGray" Width="18" Height="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<!-- END -->
<RepeatButton Grid.Row="3" Style="{StaticResource ScrollBarLineButton}" Height="18" Command="ScrollBar.LineDownCommand" Content="M 0 0 L 4 4 L 8 0 Z" />
</Grid>
</ControlTemplate>
What I am trying to do next is create an AttachedProperty to hold marker positions and bind it back to the ItemsControl.
What I don't really understand is:
- What should this attached property Type be, an ObservableCollection of int's?
- As this is a guide to the total selected items in the DataGrid, do the positions of the markers need to be scaled somehow?
- I have an attached behavior that captures DataGrid.SelectionChanged, but what about if the main collection changes there doesn't seem to be an event for this?
[EDIT]
To bind directly to the DataGrids SelectedItems. (However there is a flicker in the top of the ItemsControl when something is selected)
- Remove or comment out the SelectionChanged behavior.
- Change the ItemSource to:
ItemsSource="{Binding ElementName=GenericDataGrid, Path=SelectedItems}"
- Change Multibinding to:
<MultiBinding Converter="{StaticResource MarkerPositionConverter}">
<Binding/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}" />
<Binding Path="ActualHeight" ElementName="ItemsSelected"/>
<Binding Path="Items.Count" ElementName="GenericDataGrid"/>
</MultiBinding>
- And lastly converter to:
public class MarkerPositionConverter: IMultiValueConverter
{
//Performs the index to translate conversion
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//calculated the transform values based on the following
object o = (object)values[0];
DataGrid dg = (DataGrid)values[1];
double itemIndex = dg.Items.IndexOf(o);
double trackHeight = (double)values[2];
int itemCount = (int)values[3];
double translateDelta = trackHeight / itemCount;
return itemIndex * translateDelta;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I attempted to achieve your desired result
so for that I have made some changes, I've commented where I have made changes
add this converter reference to the resources
<helpers:MarkerPositionConverter x:Key="MarkerPositionConverter"/>
Items control xaml which is showing the markers
<!-- added Grid.Row="1", removed other attributes, removed the ItemsControlBeahviors, not much needed-->
<ItemsControl Grid.Row="1" Name="ItemsSelected"
ItemsSource="{Binding Source={x:Static helpers:MyClass.Instance}, Path=SelectedMarkers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--you can optionally bind height to scale accordingly if needed-->
<Rectangle Fill="#99708090" Width="18" Height="4">
<Rectangle.RenderTransform>
<!--added a translate transform-->
<TranslateTransform>
<TranslateTransform.Y>
<!--multi binded Y to the item and the actual height of MarkerItems control using the new MarkerPositionConverter-->
<MultiBinding Converter="{StaticResource MarkerPositionConverter}">
<Binding/>
<Binding Path="ActualHeight" ElementName="ItemsSelected"/>
</MultiBinding>
</TranslateTransform.Y>
</TranslateTransform>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
behavior.cs
public class DataGridBehaviors : Behavior<DataGrid>
{
...
void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MyClass.Instance.SelectedMarkers.Clear();
//updated item count
MyClass.Instance.ItemCount = this.AssociatedObject.Items.Count;
foreach (object o in this.AssociatedObject.SelectedItems)
MyClass.Instance.SelectedMarkers.Add(this.AssociatedObject.Items.IndexOf(o));
}
}
//removed ItemsControlBeahviors
public class MyClass : INotifyPropertyChanged
{
...
//added item count property
public int ItemCount { get; set; }
...
}
//added class to perform the index to translate conversion
public class MarkerPositionConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//calculated the transform values based on the following
double itemIndex = (double)values[0];
double trackHeight = (double)values[1];
double translateDelta = trackHeight / MyClass.Instance.ItemCount;
return itemIndex * translateDelta;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
now you may customize it as per your needs
Remove flicker
the flicker is due to the initial placement of the rectangle and before the binding gets all of its values so to avoid this initial intermittent flicker use this
<!--added fallback value to avoid intermittent value-->
<MultiBinding Converter="{StaticResource MarkerPositionConverter}" FallbackValue="-1000">
<Binding/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}" />
<Binding Path="ActualHeight" ElementName="ItemsSelected"/>
<Binding Path="Items.Count" ElementName="GenericDataGrid"/>
</MultiBinding>
so I placed the rectangle out of the view by pushing it back 1000 px when any of the binded property is busy in resolving the value or does not have any value.
and in items panel template (optional)
<ItemsPanelTemplate>
<!--added ClipToBounds to be extra safe-->
<Canvas ClipToBounds="True"/>
</ItemsPanelTemplate>
since the canvas by default does not clip its children, set ClipToBounds to true to be safe. this is necessary when the flicker is still visible somewhere in the UI even after using a huge fallback value.
来源:https://stackoverflow.com/questions/24177804/creating-an-attachedproperty-to-hold-positions-for-scrollbar-markers