c# - How to speed up rendering of vertical scrollbar markers -
c# - How to speed up rendering of vertical scrollbar markers -
i have customized vertical scrollbar displays markers selected items in datagrid.
the problem i'm facing is, when there great number of items (e.g. 5000 50000) there lag while rendering markers.
with next code renders per selected items index, number of items , height of track. inefficient , looking other solutions.
this customized vertical scrollbar
<helpers:markerpositionconverter x:key="markerpositionconverter"/> <controltemplate x:key="verticalscrollbar" 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" /> <!--start--> <itemscontrol verticalalignment="stretch" x:name="itemsselected" itemssource="{binding elementname=genericdatagrid, path=selecteditems}"> <itemscontrol.itemtemplate> <datatemplate> <rectangle fill="slategray" width="9" height="4"> <rectangle.rendertransform> <translatetransform> <translatetransform.y> <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> </translatetransform.y> </translatetransform> </rectangle.rendertransform> </rectangle> </datatemplate> </itemscontrol.itemtemplate> <itemscontrol.itemspanel> <itemspaneltemplate> <canvas cliptobounds="true"/> </itemspaneltemplate> </itemscontrol.itemspanel> </itemscontrol> <!--end--> <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> <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>
this converter transforms y position , scales accordingly if datagrid height changes.
public class markerpositionconverter: imultivalueconverter { //performs index translate conversion public object convert(object[] values, type targettype, object parameter, system.globalization.cultureinfo culture) { seek { //calculated transform values based on next 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; homecoming itemindex * translatedelta; } grab (exception ex) { console.writeline("markerpositionconverter error : " + ex.message); homecoming false; } } public object[] convertback(object value, type[] targettypes, object parameter, system.globalization.cultureinfo culture) { throw new notimplementedexception(); } }
[re-edit] have tried create separate class marker canvas, utilize observablecollection's. note @ present, not work.
xaml still same yesterday:
<helpers:markercollectioncanvas x:name="searchmarkercanvas" grid.row="1" grid="{binding relativesource={relativesource ancestortype={x:type datagrid}}}" markercollection="{binding source={x:static helpers:myclass.instance}, path=searchmarkers}"/>
canvas class, observablecollection changed utilize object instead of double, there console.writeline in markercollectioncanvas_collectionchanged never gets called:
class markercollectioncanvas : canvas { public datagrid grid { { homecoming (datagrid)getvalue(gridproperty); } set { setvalue(gridproperty, value); } } public static readonly dependencyproperty gridproperty = dependencyproperty.register("grid", typeof(datagrid), typeof(markercollectioncanvas), new propertymetadata(null)); public observablecollection<object> markercollection { { homecoming (observablecollection<object>)getvalue(markercollectionproperty); } set { setvalue(markercollectionproperty, value); } } public static readonly dependencyproperty markercollectionproperty = dependencyproperty.register("markercollection", typeof(observablecollection<object>), typeof(markercollectioncanvas), new propertymetadata(null, oncollectionchanged)); private static void oncollectionchanged(dependencyobject d, dependencypropertychangedeventargs e) { markercollectioncanvas canvas = d markercollectioncanvas; if (e.newvalue != null) { (e.newvalue observablecollection<object>).collectionchanged += canvas.markercollectioncanvas_collectionchanged; } if (e.oldvalue != null) { (e.newvalue observablecollection<object>).collectionchanged -= canvas.markercollectioncanvas_collectionchanged; } } void markercollectioncanvas_collectionchanged(object sender, notifycollectionchangedeventargs e) { console.writeline("invalidatevisual"); invalidatevisual(); } public brush markerbrush { { homecoming (brush)getvalue(markerbrushproperty); } set { setvalue(markerbrushproperty, value); } } public static readonly dependencyproperty markerbrushproperty = dependencyproperty.register("markerbrush", typeof(brush), typeof(markercollectioncanvas), new propertymetadata(brushes.darkorange)); protected override void onrender(system.windows.media.drawingcontext dc) { base.onrender(dc); if (grid == null || markercollection == null) return; //get items object[] items = new object[grid.items.count]; grid.items.copyto(items, 0); //get selected items object[] selection = new object[markercollection.count]; markercollection.copyto(selection, 0); dictionary<object, int> indexes = new dictionary<object, int>(); (int = 0; < selection.length; i++) { indexes.add(selection[i], 0); } int itemcounter = 0; (int = 0; < items.length; i++) { object item = items[i]; if (indexes.containskey(item)) { indexes[item] = i; itemcounter++; } if (itemcounter >= selection.length) break; } double translatedelta = actualheight / (double)items.length; double width = actualwidth; double height = math.max(translatedelta, 4); brush dbrush = markerbrush; double previous = 0; ienumerable<int> sortedindex = indexes.values.orderby(v => v); foreach (int itemindex in sortedindex) { double top = itemindex * translatedelta; if (top < previous) continue; dc.drawrectangle(dbrush, null, new rect(0, top, width, height)); previous = (top + height) - 1; } } }
this singleton class searchmarkers in it:
public class myclass : inotifypropertychanged { public static observablecollection<object> m_searchmarkers = new observablecollection<object>(); public observablecollection<object> searchmarkers { { homecoming m_searchmarkers; } set { m_searchmarkers = value; notifypropertychanged(); } } private static myclass m_instance; public static myclass instance { { if (m_instance == null) { m_instance = new myclass(); } homecoming m_instance; } } private myclass() { } public event propertychangedeventhandler propertychanged; private void notifypropertychanged([callermembername] string propertyname = "") { if (propertychanged != null) { propertychanged(this, new propertychangedeventargs(propertyname)); } } }
and textbox text changed behavior. observablecollection searchmarkers gets populated.
public class findtextchangedbehavior : behavior<textbox> { protected override void onattached() { base.onattached(); associatedobject.textchanged += ontextchanged; } protected override void ondetaching() { associatedobject.textchanged -= ontextchanged; base.ondetaching(); } private void ontextchanged(object sender, textchangedeventargs args) { var textbox = (sender textbox); if (textbox != null) { datagrid dg = datagridobject datagrid; string searchvalue = textbox.text; if (dg.items.count > 0) { var columnboundproperties = new list<keyvaluepair<int, string>>(); ienumerable<datagridcolumn> visiblecolumns = dg.columns.where(c => c.visibility == system.windows.visibility.visible); foreach (var col in visiblecolumns) { if (col datagridtextcolumn) { var binding = (col datagridboundcolumn).binding binding; columnboundproperties.add(new keyvaluepair<int, string>(col.displayindex, binding.path.path)); } else if (col datagridcomboboxcolumn) { datagridcomboboxcolumn dgcbc = (datagridcomboboxcolumn)col; var binding = dgcbc.selecteditembinding binding; columnboundproperties.add(new keyvaluepair<int, string>(col.displayindex, binding.path.path)); } } type itemtype = dg.items[0].gettype(); if (columnboundproperties.count > 0) { observablecollection<object> tempitems = new observablecollection<object>(); var itemssource = dg.items ienumerable; task.factory.startnew(() => { classproptextsearch.init(itemtype, columnboundproperties); if (itemssource != null) { foreach (object o in itemssource) { if (classproptextsearch.match(o, searchvalue)) { tempitems.add(o); } } } }) .continuewith(t => { application.current.dispatcher.invoke(new action(() => myclass.instance.searchmarkers = tempitems)); }); } } } } public static readonly dependencyproperty datagridobjectproperty = dependencyproperty.registerattached("datagridobject", typeof(datagrid), typeof(findtextchangedbehavior), new uipropertymetadata(null)); public object datagridobject { { homecoming (object)getvalue(datagridobjectproperty); } set { setvalue(datagridobjectproperty, value); } } }
here go, tried effort you.
created class markercanvas deriving canvas property bind info grid attached selectionchanged hear alter , requested canvas redraw invalidatevisual overrided method onrender take command of drawing , did necessary check , calculation finally rendered rectangle on calculated coordinates using given brushmarkercanvas class
class markercanvas : canvas { public datagrid grid { { homecoming (datagrid)getvalue(gridproperty); } set { setvalue(gridproperty, value); } } // using dependencyproperty backing store grid. enables animation, styling, binding, etc... public static readonly dependencyproperty gridproperty = dependencyproperty.register("grid", typeof(datagrid), typeof(markercanvas), new propertymetadata(null, ongridchanged)); private static void ongridchanged(dependencyobject d, dependencypropertychangedeventargs e) { markercanvas canvas = d markercanvas; if (e.newvalue != null) { (e.newvalue datagrid).selectionchanged += canvas.markercanvas_selectionchanged; } if (e.oldvalue != null) { (e.newvalue datagrid).selectionchanged -= canvas.markercanvas_selectionchanged; } } void markercanvas_selectionchanged(object sender, selectionchangedeventargs e) { invalidatevisual(); } public brush markerbrush { { homecoming (brush)getvalue(markerbrushproperty); } set { setvalue(markerbrushproperty, value); } } // using dependencyproperty backing store markerbrush. enables animation, styling, binding, etc... public static readonly dependencyproperty markerbrushproperty = dependencyproperty.register("markerbrush", typeof(brush), typeof(markercanvas), new propertymetadata(brushes.slategray)); protected override void onrender(system.windows.media.drawingcontext dc) { base.onrender(dc); if (grid==null || grid.selecteditems == null) return; object[] markers = grid.selecteditems.oftype<object>().toarray(); double translatedelta = actualheight / (double)grid.items.count; double width = actualwidth; double height = math.max(translatedelta, 4); brush dbrush = markerbrush; (int = 0; < markers.length; i++) { double itemindex = grid.items.indexof(markers[i]); double top = itemindex * translatedelta; dc.drawrectangle(dbrush, null, new rect(0, top, width, height)); } } }
i have adjusted height of marker grows when there less items, can take prepare specific value per needs
in xaml replace items command new marker canvas binding grid
<helpers:markercanvas grid.row="1" grid="{binding relativesource={relativesource ancestortype={x:type datagrid}}}"/>
helpers: referring wpfappdatagrid.helpers create class, can take own namespace
also can bind markerbrush property desired effet, defaulted slategray
rendering pretty fast now, perhaps create more fast doing work on indexof method.
also skip of overlapping rectangles rendered can alter method this. little buggy of now
protected override void onrender(system.windows.media.drawingcontext dc) { base.onrender(dc); if (grid==null || grid.selecteditems == null) return; object[] markers = grid.selecteditems.oftype<object>().toarray(); double translatedelta = actualheight / (double)grid.items.count; double width = actualwidth; double height = math.max(translatedelta, 4); brush dbrush = markerbrush; double previous = 0; (int = 0; < markers.length; i++) { double itemindex = grid.items.indexof(markers[i]); double top = itemindex * translatedelta; if (top < previous) continue; dc.drawrectangle(dbrush, null, new rect(0, top, width, height)); previous = (top + height) - 1; } }
performance optimization
i tried optimize performance using slight different approach, specially select button
protected override void onrender(system.windows.media.drawingcontext dc) { base.onrender(dc); if (grid == null || grid.selecteditems == null) return; object[] items = new object[grid.items.count]; grid.items.copyto(items, 0); object[] selection = new object[grid.selecteditems.count]; grid.selecteditems.copyto(selection, 0); dictionary<object, int> indexes = new dictionary<object, int>(); (int = 0; < selection.length; i++) { indexes.add(selection[i], 0); } int itemcounter = 0; (int = 0; < items.length; i++) { object item = items[i]; if (indexes.containskey(item)) { indexes[item] = i; itemcounter++; } if (itemcounter >= selection.length) break; } double translatedelta = actualheight / (double)items.length; double width = actualwidth; double height = math.max(translatedelta, 4); brush dbrush = markerbrush; double previous = 0; ienumerable<int> sortedindex = indexes.values.orderby(v => v); foreach (int itemindex in sortedindex) { double top = itemindex * translatedelta; if (top < previous) continue; dc.drawrectangle(dbrush, null, new rect(0, top, width, height)); previous = (top + height) - 1; } }
reflection approach
in have tried underlying selection list , attempted retrieve selected indexes same, added more optimization when doing select all
protected override void onrender(system.windows.media.drawingcontext dc) { base.onrender(dc); if (grid == null || grid.selecteditems == null) return; list<int> indexes = new list<int>(); double translatedelta = actualheight / (double)grid.items.count; double height = math.max(translatedelta, 4); int iteminonerect = (int)math.floor(height / translatedelta); iteminonerect -= (int)(iteminonerect * 0.2); if (grid.selecteditems.count == grid.items.count) { (int = 0; < grid.items.count; += iteminonerect) { indexes.add(i); } } else { fieldinfo fi = grid.gettype().getfield("_selecteditems", bindingflags.nonpublic | bindingflags.flattenhierarchy | bindingflags.instance); ienumerable<object> internalselectionlist = fi.getvalue(grid) ienumerable<object>; propertyinfo pi = null; int lastindex = int.minvalue; foreach (var item in internalselectionlist) { if (pi == null) { pi = item.gettype().getproperty("index", bindingflags.instance | bindingflags.nonpublic); } int newindex = (int)pi.getvalue(item); if (newindex > (lastindex + iteminonerect)) { indexes.add(newindex); lastindex = newindex; } } indexes.sort(); } double width = actualwidth; brush dbrush = markerbrush; foreach (int itemindex in indexes) { double top = itemindex * translatedelta; dc.drawrectangle(dbrush, null, new rect(0, top, width, height)); } }
c# wpf xaml rendering wpfdatagrid
Comments
Post a Comment