动态绑定到DataGridTemplateColumn的WPF

动态绑定到DataGridTemplateColumn的WPF,wpf,Wpf,在WPF中编写我的第一个项目时,我无法将注意力集中在休闲问题上 我有一个DataGrid,它使用DataSet表中的ItemSource(XML中的本地DB) 用户必须能够向DataSet/DataGrid添加列,并设置列DataTemplate,如文本、图像、日期等 因此,我必须对多个列使用单个DataTemplate,并根据列名更改绑定路径,如: <DataTemplate x:Key="ImageColumnTemplate"> <Grid>

在WPF中编写我的第一个项目时,我无法将注意力集中在休闲问题上

我有一个DataGrid,它使用DataSet表中的ItemSource(XML中的本地DB) 用户必须能够向DataSet/DataGrid添加列,并设置列DataTemplate,如文本、图像、日期等

因此,我必须对多个列使用单个DataTemplate,并根据列名更改绑定路径,如:

  <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辅助编辑行
XAML:

XamlWriter有多个限制,其中之一是写入/导出绑定,这些限制被忽略。 这是如何使其写入绑定:[source]()

对于按钮句柄PreviewMouseClick,由于另一个XmlWriter限制,无法运行。绑定到Buttom。命令无效:

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