c# WPF中通过双击编辑DataGrid中Cell的示例(附源码)
背景
在很多的时候我们需要编辑datagrid中每一个cell,编辑后保存数据,原生的wpf中的datagrid并没有提供这样的功能,今天通过一个具体的例子来实现这一个功能,在这个例子中datagrid中的数据类型可能是多种多样的,有枚举、浮点类型、布尔类型、datetime类型,每一种不同的类型需要双击以后呈现不同的效果,本文通过使用xceed.wpf.datagrid这个动态控件库来实现这个功能,当前使用的dll版本是2.5.0.0,不同的版本可能实现上面有差别,这个在使用的时候需要特别注意。
demo预览
代码结构
代码还是按照常规的mvvm结构来进行编写,主要包括views、models、mainwindowviewmodel、converters这些常规的结构,在介绍这些之前来说一下我们的整体结构,在demo中我们准备了一个四行三列的datagrid,其中第一列主要是表示当前行的名称、后面的step列是根据代码动态进行添加的,这个step部分是我们通过代码动态调整的,调整完成后能够将数据保存到数据源中,我们还是按照mvvm的结构来进行进行代码的介绍。
1 mainwindow
<window x:class="datagridcelldoubleclickdemo.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:xceed="http://schemas.xceed.com/wpf/xaml/datagrid" xmlns:models="clr-namespace:datagridcelldoubleclickdemo.models" xmlns:views="clr-namespace:datagridcelldoubleclickdemo.views" mc:ignorable="d" title="datagriddemo" height="450" width="800"> <window.resources> <datatemplate x:key="customtemplate"> <border borderthickness="1" borderbrush="blue"> <textblock text="{binding path=display }" fontweight="bold" horizontalalignment="stretch" verticalalignment="stretch" /> </border> </datatemplate> <datatemplate x:key="rowheadtemplate" datatype="{x:type models:recipecontrolvariable}"> <textblock text="{binding displayname}" horizontalalignment="stretch" verticalalignment="stretch" foreground="black" fontsize="12"/> </datatemplate> <xceed:datagridcollectionviewsource x:key="recipedata" source="{binding recipevariables}"></xceed:datagridcollectionviewsource> </window.resources> <grid> <xceed:datagridcontrol x:name="datagridcontrol" autocreatecolumns="false" fontsize="13" verticalcontentalignment="center" borderbrush="gray" borderthickness="1" itemsprimaryaxis="horizontal" pagingbehavior="lefttoright" updatesourcetrigger="cellcontentchanged" selectionunit="cell" selectionmode="single" itemssource="{binding source={staticresource recipedata}}"> <xceed:datagridcontrol.view> <xceed:tableflowview fixedcolumncount="1" containerheight="30" x:name="tblview" verticalgridlinethickness="0.5" horizontalgridlinebrush="gray" horizontalgridlinethickness="1" verticalgridlinebrush="black" rowfadeinanimationduration="0" scrollinganimationduration="0" columnstretchminwidth="10" detailindicatorwidth="20" showrowselectorpane="false" showscrolltip="false" usedefaultheadersfooters="false"> <xceed:tableflowview.fixedheaders> <datatemplate> <xceed:columnmanagerrow allowcolumnreorder="false" allowcolumnresize="true" allowdrop="false" allowsort="false" /> </datatemplate> </xceed:tableflowview.fixedheaders> </xceed:tableflowview> </xceed:datagridcontrol.view> <xceed:datagridcontrol.defaultcelleditors> <xceed:celleditor x:key="{x:type models:smartcellviewmodel}"> <xceed:celleditor.edittemplate> <datatemplate> <views:smartcelleditor content="{xceed:celleditorbinding}" verticalalignment="center" height="{binding actualheight,relativesource={relativesource ancestortype={x:type border},ancestorlevel=1}}"></views:smartcelleditor> </datatemplate> </xceed:celleditor.edittemplate> </xceed:celleditor> </xceed:datagridcontrol.defaultcelleditors> </xceed:datagridcontrol> </grid> </window>
在view部分主要是通过引用xceed中的datagridcontrol控件进行扩展的,这个里面主要是需要设置datagridcontrol的view和defaultcelleditor这个里面defaultcelleditor是本文的重点,这个就是单元格cell双击后进行编辑的主体,在这个里面我们需要指定celleditor的edittemplate,这里面需要匹配一个datatemplate,这个里面是一个smartcelleditor的子view,下面我们来看看这个smartcelleditor里面是什么内容?
2 smartcelleditor
<usercontrol x:class="datagridcelldoubleclickdemo.views.smartcelleditor" 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" xmlns:conv="clr-namespace:datagridcelldoubleclickdemo.converters" xmlns:xceed="clr-namespace:xceed.wpf.toolkit;assembly=xceed.wpf.toolkit" mc:ignorable="d" d:designheight="450" d:designwidth="800"> <usercontrol.resources> <conv:visibilityconverter x:key="visconverter" /> <conv:timespanconverter x:key="timespanconverter" /> <conv:numconverter x:key="numconverter" /> <conv:boolconverter x:key="boolconverter" /> </usercontrol.resources> <stackpanel margin="0"> <!--textblock--> <textblock x:name="textblock" background="{binding background}" foreground="{binding foreground}" text="{binding path=display,mode=oneway}" tooltip="{binding tooltip}" fontweight="{binding fontweight}" verticalalignment="stretch" visibility="{binding path=., converter={staticresource visconverter},converterparameter=textblock}"/> <!--editable combobox--> <combobox x:name="editablecombobox" itemssource="{binding smartcelldata.selections}" iseditable="true" verticalalignment="stretch" verticalcontentalignment="center" displaymemberpath="selectiondisplayname" text="{binding cellvalue,mode=twoway, updatesourcetrigger=propertychanged}" visibility="{binding path=., converter={staticresource visconverter},converterparameter=editablecombobox}" /> <!--readonly combobox--> <combobox x:name="readonlycombobox" verticalalignment="center" verticalcontentalignment="stretch" itemssource="{binding smartcelldata.selections}" iseditable="false" displaymemberpath="selectiondisplayname" selectedvaluepath="controlname" selectedvalue="{binding path=cellvalue,mode=twoway,updatesourcetrigger=propertychanged}" visibility="{binding path=., converter={staticresource visconverter},converterparameter=readonlycombobox}" /> <!--text input textbox--> <textbox horizontalcontentalignment="left" verticalalignment="stretch" verticalcontentalignment="center" text="{binding cellvalue}" visibility="{binding path=., converter={staticresource visconverter},converterparameter=textbox}" textalignment="left" /> <!--number input textbox--> <xceed:decimalupdown horizontalcontentalignment="left" verticalalignment="stretch" verticalcontentalignment="center" formatstring="g" value="{binding path=cellvalue,converter={staticresource numconverter},mode=twoway,updatesourcetrigger=propertychanged}" increment="1" visibility="{binding path=., converter={staticresource visconverter},converterparameter=decimalupdown}" textalignment="left" /> <!--checkbox--> <checkbox x:name="checkbox" verticalalignment="stretch" verticalcontentalignment="center" content="{binding tag}" ischecked="{binding path=cellvalue,converter={staticresource boolconverter},mode=twoway,updatesourcetrigger=propertychanged}" visibility="{binding path=., converter={staticresource visconverter},converterparameter=checkbox}" /> <!--timepicker--> <xceed:datetimeupdown format="custom" formatstring="hh:mm:ss" verticalalignment="stretch" verticalcontentalignment="center" value="{binding path=cellvalue,converter={staticresource timespanconverter},mode=twoway,updatesourcetrigger=propertychanged}" visibility="{binding path=., converter={staticresource visconverter},converterparameter=timepicker}" cultureinfo="uk-ua" /> </stackpanel> </usercontrol>
在这个里面我们在一个stackpanel中放置了匹配各种数据类型的template,并且每一个的visibility都是由visconverter这个自定义的converter来实现的,后面我们会分析这个converter里面的内容,这些代码的整体思想就是每次这个stackpanel里面的template都只有一个可以显示,其它的都是隐藏的,哪一个会显示是根据当前的数据类型决定的,每一个注释表示每一个类型的数据,比如我们如果定义的是bool类型,那么当我们双击单元格cell的时候会出现一个checkbox供我们编辑,所以这个里面我们需要根据我们定义的数据类型来扩展对应的模板,当我们双击单元格的时候就会显示这个模板从而进行编辑数据。
3 mainwindowviewmodel
这个里面是定义的mainwindow对应的datacontext,在这里面我们会初始化绑定到mainwindow中datagridcontrol的itemssource,我们先来看看这个里面核心的代码并就其中的要点进行分析。
using datagridcelldoubleclickdemo.models; using system; using system.collections.objectmodel; using system.windows; namespace datagridcelldoubleclickdemo { public class mainwindowviewmodel : notificationobject { public mainwindowviewmodel(xceed.wpf.datagrid.datagridcontrol datagridcontrol) { datagridcontrol = datagridcontrol; initrecipevariables(); } #region properties private observablecollection<recipecontrolvariable> _recipevariables = new observablecollection<recipecontrolvariable>(); public observablecollection<recipecontrolvariable> recipevariables { get { return _recipevariables; } set { if (value != _recipevariables) { _recipevariables = value; onpropertychanged(nameof(recipevariables)); } } } public xceed.wpf.datagrid.datagridcontrol datagridcontrol { get; set; } #endregion #region private methods private void initrecipevariables() { _recipevariables.add(new recipecontrolvariable { controlname = "name", displayname = "name", stepvalues = new observablecollection<smartcellviewmodel> { new smartcellviewmodel { cellvalue="step", errorinfo=null, smartcelldata=new recipevariableitem { controlname = "name", displayname = "name", variableeditortype=recipevariableeditortype.textinput } }, new smartcellviewmodel { cellvalue="step", errorinfo=null, smartcelldata=new recipevariableitem { controlname = "name", displayname = "name", variableeditortype=recipevariableeditortype.textinput } } } }); _recipevariables.add(new recipecontrolvariable { controlname = "time", displayname = "process time(sec)", stepvalues = new observablecollection<smartcellviewmodel> { new smartcellviewmodel { cellvalue="0", errorinfo=null, smartcelldata=new recipevariableitem { controlname = "time", displayname = "process time(sec)", variableeditortype=recipevariableeditortype.numinput } }, new smartcellviewmodel { cellvalue="0", errorinfo=null, smartcelldata=new recipevariableitem { controlname = "time", displayname = "process time(sec)", variableeditortype=recipevariableeditortype.numinput } } } }); _recipevariables.add(new recipecontrolvariable { controlname = "frontchemical", displayname = "frontchemical", stepvalues = new observablecollection<smartcellviewmodel> { new smartcellviewmodel { cellvalue="none", errorinfo=null, smartcelldata=new recipevariableitem { controlname = "frontchemical", displayname = "frontchemical", variableeditortype=recipevariableeditortype.readonlyselection, selections=new observablecollection<selectionitem> { new selectionitem { selectioncontrolname="chem1", selectiondisplayname="chem1", }, new selectionitem { selectioncontrolname="n2", selectiondisplayname="n2", }, new selectionitem { selectioncontrolname="cdiw", selectiondisplayname="cdiw", }, new selectionitem { selectioncontrolname="", selectiondisplayname="none", } } } }, new smartcellviewmodel { cellvalue="none", errorinfo=null, smartcelldata=new recipevariableitem { controlname = "frontchemical", displayname = "frontchemical", variableeditortype=recipevariableeditortype.readonlyselection, selections=new observablecollection<selectionitem> { new selectionitem { selectioncontrolname="chem1", selectiondisplayname="chem1", }, new selectionitem { selectioncontrolname="n2", selectiondisplayname="n2", }, new selectionitem { selectioncontrolname="cdiw", selectiondisplayname="cdiw", }, new selectionitem { selectioncontrolname="", selectiondisplayname="none", } } } } } }); _recipevariables.add(new recipecontrolvariable { controlname = "nozzlebindingsetting", displayname = "nozzle scan", stepvalues = new observablecollection<smartcellviewmodel> { new smartcellviewmodel { cellvalue="default", errorinfo=null, smartcelldata=new recipevariableitem { controlname = "nozzlebindingsetting", displayname = "nozzle scan", variableeditortype=recipevariableeditortype.textinput } }, new smartcellviewmodel { cellvalue="default", errorinfo=null, smartcelldata=new recipevariableitem { controlname = "nozzlebindingsetting", displayname = "nozzle scan", variableeditortype=recipevariableeditortype.textinput } } } }); } #endregion /// <summary> /// reload datagrid content /// </summary> public void refreshdatagrid() { try { if (null == datagridcontrol) return; //generate columns in grid datagridcontrol.currentcolumn = null; if (datagridcontrol.columns.count > 0) datagridcontrol.columns.clear(); var template = (datatemplate)this.datagridcontrol.findresource("customtemplate"); var rowtemplate = (datatemplate)this.datagridcontrol.findresource("rowheadtemplate"); datagridcontrol.columns.add(new xceed.wpf.datagrid.column() { width = 140, title = "name", fieldname = ".", cellcontenttemplate = rowtemplate }); var celleditor = datagridcontrol.defaultcelleditors[typeof(smartcellviewmodel)]; for (int index = 0; index < recipevariables[0].stepvalues.count; index++) { int width = 1; for (int n = 0; n < recipevariables.count; n++) { string display = recipevariables[n].stepvalues[index].display; if (!string.isnullorwhitespace(display)) { int temp = display.length * 7; width = math.max(temp, width); } } width = (int)(width * 1.1); width = math.max(width, 80); datagridcontrol.columns.add(new xceed.wpf.datagrid.column() { title = string.format("step {0}", index + 1), fieldname = string.format("stepvalues[{0}]", index), cellcontenttemplate = template, allowsort = false, width = width, maxwidth = width * 2, celleditor = celleditor }); } } catch (exception ex) { }
在这个里面我们重点分析下refreshdatagrid这个子函数,在我们的mainwindowviewmodel中我们定义的recipevariables是最终绑定到mainwindow中定义的datagridcontrol中的itemssource,是整个控件的数据源,由于我们这个datagird的第一列和后面的step列数据类型不同,所以我们的refreshdatagrid函数中增加column列的时候是分作两个部分,第一个部分是单独增加一列,后面的列是通过循环stepvalues这个集合来动态进行增加的,代码中我们定义了多少个stepvalue,那么后面就会有多少列,这个里面的重点是增加column的时候fieldname的赋值,这个是十分关键的,这个关系到能够让每一列获取到正确的数据源,例如第一列赋值filed= “.” 表示直接从当前绑定的数据源获取数据,另外后面的step列的每一个fieldname是动态进行赋值的,赋值语句是:fieldname = string.format("stepvalues[{0}]", index),这个里面index是一个动态值,这个是非常关键的一步,另外后面的step列由于需要通过双击进行编辑所以每一个column是需要赋值celleditor对象的,另外这个viewmodel中的datagridcontrol是通过构造函数进行赋值的,构造函数中的赋值就是mainwindow中定义的datagridcontrol对象,这个在阅读代码时需要特别注意。
4 models
models主要是定义的数据集合,我们的代码中主要包括recipecontrolvariable和smartviewmodel这两个部分,这两个部分分别对应datagridcontrol的数据源以及双击进行编辑的smartcelleditor两个部分一一对应。
更多代码方面的细节需要仔细去分析阅读源码,需要源码请点击此处进行下载。
以上就是c# wpf中通过双击编辑datagrid中cell的示例(附源码)的详细内容,更多关于c# wpf双击编辑datagrid的资料请关注其它相关文章!
上一篇: unity实现翻页效果
下一篇: C语言实现三子棋源代码