动态绑定到DataGridTemplateColumn的WPF
在WPF中编写我的第一个项目时,我无法将注意力集中在休闲问题上 我有一个DataGrid,它使用DataSet表中的ItemSource(XML中的本地DB) 用户必须能够向DataSet/DataGrid添加列,并设置列DataTemplate,如文本、图像、日期等 因此,我必须对多个列使用单个DataTemplate,并根据列名更改绑定路径,如:动态绑定到DataGridTemplateColumn的WPF,wpf,Wpf,在WPF中编写我的第一个项目时,我无法将注意力集中在休闲问题上 我有一个DataGrid,它使用DataSet表中的ItemSource(XML中的本地DB) 用户必须能够向DataSet/DataGrid添加列,并设置列DataTemplate,如文本、图像、日期等 因此,我必须对多个列使用单个DataTemplate,并根据列名更改绑定路径,如: <DataTemplate x:Key="ImageColumnTemplate"> <Grid>
<DataTemplate x:Key="ImageColumnTemplate">
<Grid>
<Image Source="{Binding Path=CURRENT_COLUMN_NAME Converter={StaticResource ImageReader}}" />
<TextBox Text="{Binding Path=CURRENT_COLUMN_NAME}"/>
</Grid>
</DataTemplate>
但DataGridTemplateColumn没有绑定,如果继承,DataGridBoundColumn不会写入值
你怎样才能做到这一点
编辑
请允许我在不同的背景下提出我的问题:
到目前为止,我得到的最好的:
<Window x:Class="MainWindow"
...
<Window.Resources>
<local:CellStringReader x:Key="StringReader" />
<local:CellImageReader x:Key="ImageReader" />
<Style x:Key="TextBlockToggle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, Path=IsEditing}" Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="TextBoxToggle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridCell}, Path=IsEditing}" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="ImageColumnTemplate">
<Grid Focusable="True">
<Grid HorizontalAlignment="Left" Background="Transparent">
<Button PreviewMouseDown="SelectImageFile" >
<Image x:Name="ImageTemplateImage" Height="20" Width="20"
Source="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, UpdateSourceTrigger=PropertyChanged , Converter={StaticResource ImageReader}}"/>
</Button>
</Grid>
<TextBlock x:Name="ImageTemplateTextBlock" Margin="25,0,0,0"
Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, UpdateSourceTrigger=PropertyChanged , Converter={StaticResource StringReader}}"/>
<TextBox x:Name="ImageTemplateTextBox" Margin="23,0,0,0" BorderThickness="0" Style="{StaticResource TextBoxToggle}"
Text="{Binding Mode=TwoWay, Path=., RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource StringReader}}"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
...
<DataGrid x:Name="LocalGrid" Grid.Row="1" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.RowValidationRules>
<local:RowDataValidationRule/>
</DataGrid.RowValidationRules>
</DataGrid>
...
</Grid>
</Window>
问题是,在生成的图像列中编辑文本框不会调用CellStringReader.ConvertBack(),也不会写入基础DataRow的更改值
我知道这是因为文本框绑定中的“Path=”,但我不知道还有其他选择
由于缺少上下文,在字符串中解析XAML会中断按钮PreviewMouseDown,并且它不会写入值
我的问题是如何让TextBox在DataRow中写入新值
希望它现在能引起更多的共鸣&很抱歉发了这么长的帖子。我不太明白你的一些解释 我可能采用的方法是将xaml构建为每个选项的字符串。用户选择他们想要使用的。操纵字符串并用属性名称替换占位符。然后将字符串解析为databasecolumn,然后将其添加到datagrid的columns集合中。 有一个示例可以让您了解该方法: 其中有两个.txt文件,具有未编译的“平面”轮廓xaml。 它将这些作为xml进行处理。 该示例正在构建整个datagrid,但您可以从一开始就有一个datagrid
private void Button_Click(object sender, RoutedEventArgs e)
{
// Get the datagrid shell
XElement xdg = GetXElement(@"pack://application:,,,/dg.txt");
XElement cols = xdg.Descendants().First(); // Column list
// Get the column template
XElement col = GetXElement(@"pack://application:,,,/col.txt");
DateTime mnth = DateTime.Now.AddMonths(-6);
for (int i = 0; i < 6; i++)
{
DateTime dat = mnth.AddMonths(i);
XElement el = new XElement(col);
// Month in mmm format in header
var mnthEl = el.Descendants("TextBlock")
.Single(x => x.Attribute("Text").Value.ToString() == "xxMMMxx");
mnthEl.SetAttributeValue("Text", dat.ToString("MMM"));
string monthNo = dat.AddMonths(-1).Month.ToString();
// Month as index for the product
var prodEl = el.Descendants("TextBlock")
.Single(x => x.Attribute("Text").Value == "{Binding MonthTotals[xxNumxx].Products}");
prodEl.SetAttributeValue("Text",
"{Binding MonthTotals[" + monthNo + "].Products}");
// Month as index for the total
var prodTot = el.Descendants("TextBlock")
.Single(x => x.Attribute("Text").Value == "{Binding MonthTotals[xxNumxx].Total}");
prodTot.SetAttributeValue("Text",
"{Binding MonthTotals[" + monthNo + "].Total}");
cols.Add(el);
}
string dgString = xdg.ToString();
ParserContext context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
DataGrid dg = (DataGrid)XamlReader.Parse(dgString, context);
Root.Children.Add(dg);
}
private XElement GetXElement(string uri)
{
XDocument xmlDoc = new XDocument();
var xmltxt = Application.GetContentStream(new Uri(uri));
string elfull = new StreamReader(xmltxt.Stream).ReadToEnd();
xmlDoc = XDocument.Parse(elfull);
return xmlDoc.Root;
}
private void按钮\u单击(对象发送者,路由目标)
{
//获取datagrid外壳
XElement xdg=GetXElement(@)pack://application:,,,/dg.txt);
XElement cols=xdg.subjects().First();//列列表
//获取列模板
XElement col=GetXElement(@)pack://application:,,,/col.txt“;
DateTime mnth=DateTime.Now.AddMonths(-6);
对于(int i=0;i<6;i++)
{
DateTime dat=mnth.AddMonths(i);
XElement el=新XElement(col);
//标题中mmm格式的月份
var mnthEl=el.substands(“TextBlock”)
.Single(x=>x.Attribute(“Text”).Value.ToString()==“xxMMMxx”);
mnthEl.SetAttributeValue(“Text”,dat.ToString(“MMM”);
字符串monthNo=dat.AddMonths(-1.Month.ToString();
//月份作为产品的索引
var prodEl=el.substands(“TextBlock”)
.Single(x=>x.Attribute(“Text”).Value==“{Binding MonthTotals[xxNumxx].Products}”);
prodEl.SetAttributeValue(“文本”,
“{具有约束力的MonthTotals[“+monthNo+”].Products}”);
//月份作为总数的索引
var prodTot=el.substands(“TextBlock”)
.Single(x=>x.Attribute(“Text”).Value==“{Binding MonthTotals[xxNumxx].Total}”);
prodTot.SetAttributeValue(“文本”,
“{有约束力的月份[“+monthNo+”].Total}”;
cols.Add(el);
}
字符串dgString=xdg.ToString();
ParserContext上下文=新的ParserContext();
context.xmlnsdirectionary.Add(“,”http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.xmlnsdirectionary.Add(“x”http://schemas.microsoft.com/winfx/2006/xaml");
DataGrid dg=(DataGrid)XamlReader.Parse(dgString,context);
Root.Children.Add(dg);
}
私有XElement GetXElement(字符串uri)
{
XDocument xmlDoc=新XDocument();
var xmltxt=Application.GetContentStream(新Uri(Uri));
字符串elfull=newstreamreader(xmltxt.Stream).ReadToEnd();
xmlDoc=XDocument.Parse(elfull);
返回xmlDoc.Root;
}
您也可以使用string.replace。
或者两者都有。血腥的胜利
综上所述,所需的功能是:
- 在DataGrid中对多个列使用单个DataTemlate
- DataTemplate需要双向绑定,并且必须能够写入底层DataRow对象
- 使用OpenFileDialog辅助编辑行
Public ReadOnly Property ClickCommand As ICommand = New CommandHandler(AddressOf SelectImageFile, True)
Public Class CommandHandler
Implements ICommand
Private _action As Action
Private _canExecute As Boolean
Public Sub New(ByVal action As Action, ByVal canExecute As Boolean)
_action = action
_canExecute = canExecute
End Sub
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
_action()
End Sub
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
Return _canExecute
End Function
Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
End Class
Public Sub SelectImageFile()
If LocalGrid.CurrentColumn Is Nothing Then Exit Sub
Dim fd As OpenFileDialog = New OpenFileDialog()
fd.ShowDialog()
Dim Row As DataRowView = LocalGrid.CurrentItem
Row.Item(LocalGrid.CurrentColumn.Header) = fd.FileName
LocalGrid.CommitEdit()
BaseGrid_RowEditEnding()
End Sub
这是可行的,但是任何关于如何缩短的建议都是受欢迎的。谢谢您的回答,但我无法使生成的列可写。我在col.txt DataTemplate中将TextBlock改为TextBox,并使DataGrid不再是只读的,而且什么都不起作用。saller.monhtotals中的值没有更改。有什么好办法解决这个问题吗?你有没有把期望查找TextBlock的代码改成TextBox?var prodEl=el.substands(“TextBlock”)当然可以。我没有例外,值显示正确。唯一的问题是当更改文本框中的值时,更改不会写入源属性/对象中。我不明白为什么这与您的问题有关。它没有更新,因为绑定正在绑定集合中的集合上使用索引。每个销售员都有一份总销售额清单,每个月有一个条目。datagrid是只读的,图是只读的。它们是从销售中产生的总数。很抱歉让你们困惑,我编辑了原始问题,现在应该会有更多的降调了。请看一看。templatecolumn中的内容可以绑定到任意数量的属性。这是一笔资金
private void Button_Click(object sender, RoutedEventArgs e)
{
// Get the datagrid shell
XElement xdg = GetXElement(@"pack://application:,,,/dg.txt");
XElement cols = xdg.Descendants().First(); // Column list
// Get the column template
XElement col = GetXElement(@"pack://application:,,,/col.txt");
DateTime mnth = DateTime.Now.AddMonths(-6);
for (int i = 0; i < 6; i++)
{
DateTime dat = mnth.AddMonths(i);
XElement el = new XElement(col);
// Month in mmm format in header
var mnthEl = el.Descendants("TextBlock")
.Single(x => x.Attribute("Text").Value.ToString() == "xxMMMxx");
mnthEl.SetAttributeValue("Text", dat.ToString("MMM"));
string monthNo = dat.AddMonths(-1).Month.ToString();
// Month as index for the product
var prodEl = el.Descendants("TextBlock")
.Single(x => x.Attribute("Text").Value == "{Binding MonthTotals[xxNumxx].Products}");
prodEl.SetAttributeValue("Text",
"{Binding MonthTotals[" + monthNo + "].Products}");
// Month as index for the total
var prodTot = el.Descendants("TextBlock")
.Single(x => x.Attribute("Text").Value == "{Binding MonthTotals[xxNumxx].Total}");
prodTot.SetAttributeValue("Text",
"{Binding MonthTotals[" + monthNo + "].Total}");
cols.Add(el);
}
string dgString = xdg.ToString();
ParserContext context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
DataGrid dg = (DataGrid)XamlReader.Parse(dgString, context);
Root.Children.Add(dg);
}
private XElement GetXElement(string uri)
{
XDocument xmlDoc = new XDocument();
var xmltxt = Application.GetContentStream(new Uri(uri));
string elfull = new StreamReader(xmltxt.Stream).ReadToEnd();
xmlDoc = XDocument.Parse(elfull);
return xmlDoc.Root;
}
<Window x:Class="MainWindow"
...
<Window.Resources>
<local:ImageReader x:Key="ImageReader" />
...
<DataTemplate x:Key="ImageColumnReadTemplate">
<Grid>
<Grid HorizontalAlignment="Left" Background="Transparent">
<Button IsEnabled="False" >
<Image x:Name="ImageTemplateImage" Height="18" Width="18" Source="{Binding Path=COLUMN_NAME, Converter={StaticResource ImageReader}}" />
</Button>
</Grid>
<TextBlock x:Name="ImageTemplateTextBlock" Margin="25,0,0,0" Text="{Binding Path=COLUMN_NAME}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ImageColumnWriteTemplate">
<Grid>
<Grid HorizontalAlignment="Left" Background="Transparent">
<Button Command="{Binding ClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" >
<Image x:Name="ImageTemplateImage" Height="18" Width="18" Source="{Binding Path=COLUMN_NAME, Converter={StaticResource ImageReader}}" />
</Button>
</Grid>
<TextBox x:Name="ImageTemplateTextBox" Margin="23,0,0,0" BorderThickness="0" Text="{Binding Path=COLUMN_NAME}"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
...
<DataGrid x:Name="LocalGrid" Grid.Row="1" AutoGenerateColumns="False" CanUserAddRows="False">
...
</DataGrid>
...
</Grid>
</Window>
For Each fColumn As DataColumn In LocalTable.Columns
Dim ImageColumn As New DataGridTemplateColumn With {.Header = fColumn.ColumnName}
ImageColumn.CellTemplate = CreateTemplate("ImageColumnReadTemplate", fColumn.ColumnName)
ImageColumn.CellEditingTemplate = CreateTemplate("ImageColumnWriteTemplate", fColumn.ColumnName)
LocalGrid.Columns.Add(ImageColumn)
Next
Private Function CreateTemplate(TemplateName As String, ColumnName As String) As DataTemplate
Dim Template As DataTemplate = Me.FindResource(TemplateName)
Dim StrBuilder = New StringBuilder()
Dim Settings = New XmlWriterSettings() With {.Indent = True, .OmitXmlDeclaration = True}
Dim dsm = New XamlDesignerSerializationManager(XmlWriter.Create(StrBuilder, Settings)) With {.XamlWriterMode = XamlWriterMode.Expression}
XamlWriter.Save(Template, dsm)
StrBuilder = StrBuilder.Replace("COLUMN_NAME", ColumnName)
Dim xmlDoc = XDocument.Parse(StrBuilder.ToString())
'IO.File.WriteAllLines("D:\xml.txt", xmlDoc.ToString.Split(vbNewLine)) 'Debug
Dim NewTemplate As DataTemplate = XamlReader.Parse(xmlDoc.ToString())
Return NewTemplate
End Function
Imports System.ComponentModel
Imports System.Windows.Markup
Class BindingConvertor
Inherits ExpressionConverter
Public Overrides Function CanConvertTo(ByVal context As ITypeDescriptorContext, ByVal destinationType As Type) As Boolean
If destinationType = GetType(MarkupExtension) Then
Return True
Else
Return False
End If
End Function
Public Overrides Function ConvertTo(ByVal context As ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As Type) As Object
If destinationType = GetType(MarkupExtension) Then
Dim bindingExpression As BindingExpression = TryCast(value, BindingExpression)
If bindingExpression Is Nothing Then Throw New Exception()
Return bindingExpression.ParentBinding
End If
Return MyBase.ConvertTo(context, culture, value, destinationType)
End Function
End Class
Module EditorHelper
Sub RegisterBindingConvertor
EditorHelper.Register(Of BindingExpression, BindingConvertor)()
End Sub
Sub Register(Of T, TC)()
Dim attr As Attribute() = New Attribute(0) {}
Dim vConv As TypeConverterAttribute = New TypeConverterAttribute(GetType(TC))
attr(0) = vConv
TypeDescriptor.AddAttributes(GetType(T), attr)
End Sub
End Module
Class MainWindow
Public Sub New()
EditorHelper.RegisterBindingConvertor()
'...
End Sub
'...
End Class
Public ReadOnly Property ClickCommand As ICommand = New CommandHandler(AddressOf SelectImageFile, True)
Public Class CommandHandler
Implements ICommand
Private _action As Action
Private _canExecute As Boolean
Public Sub New(ByVal action As Action, ByVal canExecute As Boolean)
_action = action
_canExecute = canExecute
End Sub
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
_action()
End Sub
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
Return _canExecute
End Function
Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
End Class
Public Sub SelectImageFile()
If LocalGrid.CurrentColumn Is Nothing Then Exit Sub
Dim fd As OpenFileDialog = New OpenFileDialog()
fd.ShowDialog()
Dim Row As DataRowView = LocalGrid.CurrentItem
Row.Item(LocalGrid.CurrentColumn.Header) = fd.FileName
LocalGrid.CommitEdit()
BaseGrid_RowEditEnding()
End Sub