/// <summary>
/// Dev Express GridControl does not support multi selection when Master-Detail rows are involved. This behavior highlights multiple rows
/// and persist onto ViewModel. So it has the effect of multiple selection.
/// </summary>
public class MultiRowHighlightBehavior : Behavior<GridControl>
{
#region properties
public int MaxNumberOfRowsHighlighted
{
get { return (int)this.GetValue(MaxNumberOfRowsHighlightedProperty); }
set { this.SetValue(MaxNumberOfRowsHighlightedProperty, value); }
}
public static readonly DependencyProperty MaxNumberOfRowsHighlightedProperty = DependencyProperty.Register(
"MaxNumberOfRowsHighlighted", typeof(int), typeof(MultiRowHighlightBehavior), new PropertyMetadata(int.MaxValue));
// use IList, INotifyCollectionChanged to avoid generic in DP def e.g. ObservableCollection<T>
// Pass highlighted Data Rows to VM
public IList ViewModelHighlightedRows
{
get { return (IList)this.GetValue(ViewModelHighlightedRowsProperty); }
set { this.SetValue(ViewModelHighlightedRowsProperty, value); }
}
public static readonly DependencyProperty ViewModelHighlightedRowsProperty = DependencyProperty.Register(
"ViewModelHighlightedRows", typeof(IList), typeof(MultiRowHighlightBehavior), new PropertyMetadata(null));
// need to pass in Data Rows for the GridControl, so any removal triggers highlighting updates due to row position changes.
public INotifyCollectionChanged ViewModelDataRows
{
get { return (INotifyCollectionChanged)this.GetValue(ViewModelDataRowsProperty); }
set { this.SetValue(ViewModelDataRowsProperty, value); }
}
public static readonly DependencyProperty ViewModelDataRowsProperty = DependencyProperty.Register(
"ViewModelDataRows", typeof(INotifyCollectionChanged), typeof(MultiRowHighlightBehavior), new PropertyMetadata(null));
public string HighLightingColorHex
{
get { return (string)this.GetValue(HighLightingColorHexProperty); }
set { this.SetValue(HighLightingColorHexProperty, value); }
}
public static readonly DependencyProperty HighLightingColorHexProperty = DependencyProperty.Register(
"HighLightingColorHex", typeof(string), typeof(MultiRowHighlightBehavior), new PropertyMetadata(DefaultColorHex));
public TableViewHitTest HighlightingClickArea
{
get { return (TableViewHitTest)this.GetValue(HighlightingClickAreaProperty); }
set { this.SetValue(HighlightingClickAreaProperty, value); }
}
public static readonly DependencyProperty HighlightingClickAreaProperty = DependencyProperty.Register(
"HighlightingClickArea", typeof(TableViewHitTest), typeof(MultiRowHighlightBehavior), new PropertyMetadata(TableViewHitTest.RowIndicator));
public bool AllowHighLightingMasterRow
{
get { return (bool)this.GetValue(AllowHighLightingMasterRowProperty); }
set { this.SetValue(AllowHighLightingMasterRowProperty, value); }
}
public static readonly DependencyProperty AllowHighLightingMasterRowProperty = DependencyProperty.Register(
"AllowHighLightingMasterRow", typeof(bool), typeof(MultiRowHighlightBehavior), new PropertyMetadata(false));
public bool UseShaderEffect
{
get { return (bool)this.GetValue(UseShaderEffectProperty); }
set { this.SetValue(UseShaderEffectProperty, value); }
}
public static readonly DependencyProperty UseShaderEffectProperty = DependencyProperty.Register(
"UseShaderEffect", typeof(bool), typeof(MultiRowHighlightBehavior), new PropertyMetadata(true));
private const string DefaultColorHex = "#FF3D3D3D";
private SolidColorBrush _highLightingBrush;
private enum HighlightingState
{
On,
Off
}
#endregion
#region event
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
AssociatedObject.Loaded += AssociatedObject_Loaded;
TrySetHighlightingBrush();
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
var sv = VisualTreeHelpers.FindChild<ScrollViewer>(AssociatedObject);
if (sv != null)
{
sv.ScrollChanged += (s, _) => { if (ViewModelHighlightedRows.Count > 0) RedoHilighting(); };
}
}
protected override void OnChanged()
{
base.OnChanged();
if (ViewModelDataRows != null)
{
ViewModelDataRows.CollectionChanged += ViewModelDataRows_CollectionChanged;
}
if (ViewModelHighlightedRows is INotifyCollectionChanged) // e.g ObservableCollection<T> is IList and INotifyCollectionChanged
{
(ViewModelHighlightedRows as INotifyCollectionChanged).CollectionChanged += ViewModelHighlightedRows_CollectionChanged;
}
}
void ViewModelHighlightedRows_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (ViewModelHighlightedRows.Count == 0) RedoHilighting(); // clear all highlights if collection cleared by outside caller.
}
// Rows removed by VM will be removed from highlighted rows if present
void ViewModelDataRows_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var i in e.OldItems)
{
if (ViewModelHighlightedRows != null) ViewModelHighlightedRows.Remove(i);
}
RedoHilighting();
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
RedoHilighting();
}
}
void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (AssociatedObject.View == null) return;
var view = AssociatedObject.View as TableView;
if (view == null) return;
TableViewHitInfo hitInfo = view.CalcHitInfo(e.OriginalSource as DependencyObject);
if (hitInfo == null) return;
// Highlighting happens if user click on Row Header. So highlighting will not interfare with Row/Cell/Expander selection/expanding functionality
if (hitInfo.HitTest == HighlightingClickArea)
{
var row = view.GetRowElementByMouseEventArgs(e) as GridRow;
if (row == null || row.RowDataContent == null) return;
var rowData = row.RowDataContent.DataContext as RowData;
if (rowData == null || ViewModelHighlightedRows == null) return;
if (!AllowHighLightingMasterRow && IsMasterRow(row) && !HasMarketDepth(rowData.Row)) return; // use visual tree to check if row =master row, no API found
if (ViewModelHighlightedRows.Contains(rowData.Row))
{
ToggleHighlighting(row, HighlightingState.Off);
ViewModelHighlightedRows.Remove(rowData.Row);
}
else
{
if (ViewModelHighlightedRows.Count >= MaxNumberOfRowsHighlighted) return;
ToggleHighlighting(row, HighlightingState.On);
ViewModelHighlightedRows.Add(rowData.Row);
}
}
}
private bool HasMarketDepth(object rowDataObject)
{
try
{
PropertyInfo propInfo = rowDataObject.GetType().GetProperty("HasMarketDepth", BindingFlags.Instance | BindingFlags.Public);
if (propInfo != null)
{
var val = propInfo.GetValue(rowDataObject, null);
return (bool)val;
}
}
catch (Exception)
{
}
return false;
}
#endregion
#region change View and VM, etc.
// change view only, not ViewModel
private void ToggleHighlighting(GridRow row, HighlightingState hState)
{
try
{
var rowIndControl = VisualTreeHelpers.FindChild<RowIndicatorControl>(row);
var ricBorder = VisualTreeHelpers.FindChild<Border>(rowIndControl);
SolidColorBrush toggleBrush;
Effect toggleEff;
double toggleThickness;
if (hState == HighlightingState.On)
{
toggleBrush = _highLightingBrush;
toggleThickness = 1;
toggleEff = new ColorToneEffect() { DarkColor = Colors.Transparent, LightColor = Colors.LightBlue, ToneAmount = 0.1 };
}
else
{
toggleBrush = new SolidColorBrush(Colors.Transparent);
toggleThickness = 0;
toggleEff = null;
}
var rowContentBorder = VisualTreeHelpers.FindChild<Border>(row, "RowContentBorder");
var fixNoneCellstBorder = VisualTreeHelpers.FindChild<Border>(row, "PART_FixedNoneCellsBorder");
var detailButtonBorder = VisualTreeHelpers.FindChild<Border>(row, "PART_DetailButtonBorder");
if (UseShaderEffect) // Shader effect will highlighting on top of all colors across a row
{
ricBorder.Effect = toggleEff;
rowContentBorder.Effect = toggleEff;
fixNoneCellstBorder.Effect = toggleEff;
detailButtonBorder.Effect = toggleEff;
}
else
{
ricBorder.Background = toggleBrush;
ricBorder.BorderThickness = new Thickness(toggleThickness, toggleThickness, 0, toggleThickness);
ricBorder.BorderBrush = toggleBrush;
rowContentBorder.BorderBrush = toggleBrush;
rowContentBorder.BorderThickness = new Thickness(0, toggleThickness, 0, toggleThickness < 0.5 ? 0.0 : 1.0);
fixNoneCellstBorder.BorderBrush = toggleBrush;
fixNoneCellstBorder.BorderThickness = new Thickness(0, toggleThickness, 0,
toggleThickness < 0.5 ? 0.0 : 1.0);
detailButtonBorder.BorderBrush = toggleBrush;
detailButtonBorder.BorderThickness = new Thickness(0, toggleThickness, 0, toggleThickness);
}
}
catch (Exception)
{
}
}
private static bool IsMasterRow(GridRow row)
{
try
{
var masterRowExpandBorder = VisualTreeHelpers.FindChild<Border>(row, "PART_DetailButtonBorder");
if (masterRowExpandBorder == null) return false;
var isMasterRow = VisualTreeHelpers.FindChild<Grid>(masterRowExpandBorder) != null;
return isMasterRow;
}
catch (Exception)
{
return false; // if not sure, let user highlight and un-highlight
}
}
void RedoHilighting()
{
if (AssociatedObject == null || AssociatedObject.View == null) return;
var view = AssociatedObject.View as TableView;
if (view == null) return;
var hp = VisualTreeHelpers.FindChild<HierarchyPanel>(view);
if (hp == null) return;
AssociatedObject.RefreshData();
foreach (var c in hp.Children)
{
var row = c as GridRow;
if (row == null || row.RowDataContent == null) continue;
var rowData = row.RowDataContent.DataContext as RowData;
if (rowData == null) continue;
if (ViewModelHighlightedRows.Contains(rowData.Row))
{
ToggleHighlighting(row, HighlightingState.On);
}
else
{
ToggleHighlighting(row, HighlightingState.Off);
}
}
}
private void TrySetHighlightingBrush()
{
try
{
_highLightingBrush = (new BrushConverter().ConvertFrom(HighLightingColorHex)) as SolidColorBrush;
}
catch (Exception)
{
_highLightingBrush = (new BrushConverter().ConvertFrom(DefaultColorHex)) as SolidColorBrush;
}
if (_highLightingBrush != null) _highLightingBrush.Freeze();
}
#endregion
}
Tuesday, November 15, 2016
Using Pixel shader to highlight data grid row
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment