c# WPF中通過(guò)雙擊編輯DataGrid中Cell的示例(附源碼)
背景
在很多的時(shí)候我們需要編輯DataGrid中每一個(gè)Cell,編輯后保存數(shù)據(jù),原生的WPF中的DataGrid并沒(méi)有提供這樣的功能,今天通過(guò)一個(gè)具體的例子來(lái)實(shí)現(xiàn)這一個(gè)功能,在這個(gè)例子中DataGrid中的數(shù)據(jù)類型可能是多種多樣的,有枚舉、浮點(diǎn)類型、布爾類型、DateTime類型,每一種不同的類型需要雙擊以后呈現(xiàn)不同的效果,本文通過(guò)使用Xceed.Wpf.DataGrid這個(gè)動(dòng)態(tài)控件庫(kù)來(lái)實(shí)現(xiàn)這個(gè)功能,當(dāng)前使用的Dll版本是2.5.0.0,不同的版本可能實(shí)現(xiàn)上面有差別,這個(gè)在使用的時(shí)候需要特別注意。
Demo預(yù)覽

代碼結(jié)構(gòu)
代碼還是按照常規(guī)的MVVM結(jié)構(gòu)來(lái)進(jìn)行編寫,主要包括Views、Models、MainWindowViewModel、Converters這些常規(guī)的結(jié)構(gòu),在介紹這些之前來(lái)說(shuō)一下我們的整體結(jié)構(gòu),在Demo中我們準(zhǔn)備了一個(gè)四行三列的DataGrid,其中第一列主要是表示當(dāng)前行的名稱、后面的Step列是根據(jù)代碼動(dòng)態(tài)進(jìn)行添加的,這個(gè)Step部分是我們通過(guò)代碼動(dòng)態(tài)調(diào)整的,調(diào)整完成后能夠?qū)?shù)據(jù)保存到數(shù)據(jù)源中,我們還是按照MVVM的結(jié)構(gòu)來(lái)進(jìn)行進(jìn)行代碼的介紹。
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部分主要是通過(guò)引用Xceed中的DataGridControl控件進(jìn)行擴(kuò)展的,這個(gè)里面主要是需要設(shè)置DataGridControl的View和DefaultCellEditor這個(gè)里面DefaultCellEditor是本文的重點(diǎn),這個(gè)就是單元格Cell雙擊后進(jìn)行編輯的主體,在這個(gè)里面我們需要指定CellEditor的EditTemplate,這里面需要匹配一個(gè)DataTemplate,這個(gè)里面是一個(gè)SmartCellEditor的子View,下面我們來(lái)看看這個(gè)SmartCellEditor里面是什么內(nèi)容?
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>
在這個(gè)里面我們?cè)谝粋€(gè)StackPanel中放置了匹配各種數(shù)據(jù)類型的Template,并且每一個(gè)的Visibility都是由visConverter這個(gè)自定義的Converter來(lái)實(shí)現(xiàn)的,后面我們會(huì)分析這個(gè)Converter里面的內(nèi)容,這些代碼的整體思想就是每次這個(gè)StackPanel里面的Template都只有一個(gè)可以顯示,其它的都是隱藏的,哪一個(gè)會(huì)顯示是根據(jù)當(dāng)前的數(shù)據(jù)類型決定的,每一個(gè)注釋表示每一個(gè)類型的數(shù)據(jù),比如我們?nèi)绻x的是Bool類型,那么當(dāng)我們雙擊單元格Cell的時(shí)候會(huì)出現(xiàn)一個(gè)CheckBox供我們編輯,所以這個(gè)里面我們需要根據(jù)我們定義的數(shù)據(jù)類型來(lái)擴(kuò)展對(duì)應(yīng)的模板,當(dāng)我們雙擊單元格的時(shí)候就會(huì)顯示這個(gè)模板從而進(jìn)行編輯數(shù)據(jù)。
3 MainWindowViewModel
這個(gè)里面是定義的MainWindow對(duì)應(yīng)的DataContext,在這里面我們會(huì)初始化綁定到MainWindow中DataGridControl的ItemsSource,我們先來(lái)看看這個(gè)里面核心的代碼并就其中的要點(diǎn)進(jìn)行分析。
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)
{
}
在這個(gè)里面我們重點(diǎn)分析下RefreshDataGrid這個(gè)子函數(shù),在我們的MainWindowViewModel中我們定義的RecipeVariables是最終綁定到MainWindow中定義的DataGridControl中的ItemsSource,是整個(gè)控件的數(shù)據(jù)源,由于我們這個(gè)DataGird的第一列和后面的Step列數(shù)據(jù)類型不同,所以我們的RefreshDataGrid函數(shù)中增加Column列的時(shí)候是分作兩個(gè)部分,第一個(gè)部分是單獨(dú)增加一列,后面的列是通過(guò)循環(huán)StepValues這個(gè)集合來(lái)動(dòng)態(tài)進(jìn)行增加的,代碼中我們定義了多少個(gè)StepValue,那么后面就會(huì)有多少列,這個(gè)里面的重點(diǎn)是增加Column的時(shí)候FieldName的賦值,這個(gè)是十分關(guān)鍵的,這個(gè)關(guān)系到能夠讓每一列獲取到正確的數(shù)據(jù)源,例如第一列賦值Filed= “.” 表示直接從當(dāng)前綁定的數(shù)據(jù)源獲取數(shù)據(jù),另外后面的Step列的每一個(gè)FieldName是動(dòng)態(tài)進(jìn)行賦值的,賦值語(yǔ)句是:FieldName = string.Format("StepValues[{0}]", index),這個(gè)里面Index是一個(gè)動(dòng)態(tài)值,這個(gè)是非常關(guān)鍵的一步,另外后面的Step列由于需要通過(guò)雙擊進(jìn)行編輯所以每一個(gè)Column是需要賦值CellEditor對(duì)象的,另外這個(gè)ViewModel中的DataGridControl是通過(guò)構(gòu)造函數(shù)進(jìn)行賦值的,構(gòu)造函數(shù)中的賦值就是MainWindow中定義的DataGridControl對(duì)象,這個(gè)在閱讀代碼時(shí)需要特別注意。
4 Models
Models主要是定義的數(shù)據(jù)集合,我們的代碼中主要包括RecipeControlVariable和SmartViewModel這兩個(gè)部分,這兩個(gè)部分分別對(duì)應(yīng)DataGridControl的數(shù)據(jù)源以及雙擊進(jìn)行編輯的SmartCellEditor兩個(gè)部分一一對(duì)應(yīng)。
更多代碼方面的細(xì)節(jié)需要仔細(xì)去分析閱讀源碼,需要源碼請(qǐng)點(diǎn)擊此處進(jìn)行下載。
以上就是c# WPF中通過(guò)雙擊編輯DataGrid中Cell的示例(附源碼)的詳細(xì)內(nèi)容,更多關(guān)于c# wpf雙擊編輯DataGrid的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于WPF實(shí)現(xiàn)路徑圖標(biāo)控件
這篇文章主要介紹了如何利用WPF實(shí)現(xiàn)路徑圖標(biāo)控件,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定幫助,需要的小伙伴可以參考一下2023-07-07
asp.net獲取系統(tǒng)當(dāng)前時(shí)間的方法詳解
這篇文章主要介紹了asp.net獲取系統(tǒng)當(dāng)前時(shí)間的方法,較為詳細(xì)的分析了C#日期與時(shí)間操作所涉及的相關(guān)函數(shù)與使用技巧,需要的朋友可以參考下2016-06-06
C#使用Directoryinfo類獲得目錄信息和屬性的方法
這篇文章主要介紹了C#使用Directoryinfo類獲得目錄信息和屬性的方法,涉及C#操作目錄的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
C# mysql 插入數(shù)據(jù),中文亂碼的解決方法
用C#操作mysql時(shí), 插入數(shù)據(jù)中文都是亂碼,只顯示問(wèn)號(hào),數(shù)據(jù)庫(kù)本身使用的是utf-8字符2013-10-10

