c# WPF中通过双击编辑DataGrid中Cell的示例(附源码)
- 作者: 李二狗他爹
- 来源: 51数据库
- 2021-08-28
背景
在很多的时候我们需要编辑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的资料请关注其它相关文章!
