В настройка с AutoGenerateColmns=True
се опитвам да намеря начин, който да ми позволи да показвам многоцветен текст в една клетка на C# DataGrid
в WPF. Както решение с персонализиран шаблон на клетка, така и с HTML интерпретатор в клетката би било подходящо; поради технически причини бих предпочел първия вариант, но прекарах много време в опити да накарам някое от тези да работи, но без резултат.
Допълнителен проблем е, че имам работа с няколко таблици тук, които трябва да покажа, използвайки същия потребителски контрол. Само една от таблиците ще изисква форматиране на текст. Разбрах как да променя динамично естеството на таблицата въз основа на критерий по мой избор, но не знам как да попълня тези модифицирани клетки въз основа на действителния текст, който влиза там. Самият текст съдържа токен ("|||"
—три тръби), при който да се раздели низът; единият трябва да бъде показан в синьо, другият в червено.
Нека ви покажа моя код. Ще започна с извадка от App.xaml
, за да е ясно как съм настроил своя DataGrid
стилово.
My App.xaml
Както ще видите, трябва да накарам DataGrid
да генерира автоматично колоните, тъй като работя върху база данни, чиято схема не ми е известна по време на компилиране. Тъй като заглавките на таблицата може да съдържат долни черти, създадох персонализиран шаблон, който не третира долните черти по обичайния начин, т.е. като комбинации от кратки клавиши с Alt.
<Style x:Key="DataGridStyle" TargetType="DataGrid">
<Setter Property="AutoGenerateColumns" Value="True" />
<Setter Property="CanUserAddRows" Value="False" />
<Setter Property="ColumnHeaderStyle">
<Setter.Value>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{TemplateBinding Content}"
HorizontalAlignment="Center"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<Setter Property="CanUserSortColumns" Value="True" />
</Style>
Самият DataGrid
Това има някои модификации, една от които е, че използвам персонализиран RowDetailsTemplate
въз основа на друг потребителски контрол. Това не е от значение (надявам се), но го оставям тук за пълнота (и в случай, че има някои странични ефекти, които пропускам). ExpView
е пространството от имена, в което работя; Самият „Exp“ е това, което се нарича софтуерът (поради което предшественикът на DataGrid
се нарича „ExpView:ExpResultsTable
“).
Виждате, че съм абониран за две събития тук. Опитвах се колкото е възможно повече да запазя кода си празен (както се препоръчва в документите на MSDN за чиста имплементация на MVVM), но страхувам се, че човек може да направи толкова много само с XAML... Чувствайте се свободни да ме коригирате, все пак. Тези събития са това, което използвам, за да определя дали DataGrid
ще бъде за специалната маса или за нормалните.
<DataGrid Name="DataGrid_Calibs"
Style="{StaticResource DataGridStyle}"
SelectedItem="{Binding Path=SelectedCalib,
RelativeSource={RelativeSource AncestorType=ExpView:ExpResultsTable}}"
AutoGeneratingColumn="DataGrid_Calibs_AutoGeneratingColumn"
LoadingRow="DataGrid_Calibs_LoadingRow">
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<ExpView:CalibrationDetails/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
В началото на този XAML файл имам и следната декларация за ресурс, която влиза в клетките, в случай че трябва да покажа „специалната“ таблица.
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="DifferenceDataTemplate">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="FieldValue_Old" Foreground="Blue"/>
<TextBlock x:Name="FieldValue_New" Foreground="Red"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
Планът е таблицата да се попълни с нормално обвързване на данни, ако трябва да показва нормални данни. (Аз не попълвам DataGrid
с RowView
s; вместо това обвързвам DataGrid
към обекти, които правилно показват публични свойства. Ще видите ефекта от това в моя код за обработка на събития по-долу.) Ако данните са „специални“, Искам да заменя клетките с StackPanel
, дефинирани в ресурсите по-горе, и да задам съдържанието на двете TextBlock
s (или Label
s, ако се окаже, че работят по-добре) по отношение на първата и втората част от низа, които трябва да влязат в клетка, разделена на "|||"
.
Обработчиците на събития в задния код
Хубавото на WPF манипулатора на събития за автоматично генериране на колони на DataGrid
е, че той върши нормалната си работа, освен ако изрично не презапишете колоната. Ето защо следният код работи добре при замяната на клетките на DataGrid
с гореспоменатите StackPanel
само в случай, че открия „специална таблица“.
private void DataGrid_Calibs_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) {
ParameterDetails parameterDetailsControl = ((ScrollViewer)((DockPanel)((StackPanel)((DataGrid)sender).Parent).Parent).Parent).Parent as ParameterDetails;
ExpUserInterface parent = Window.GetWindow((DependencyObject)parameterDetailsControl) as ExpUserInterface;
ExpResultsTable resultsTableControl = parent.ResultsTable.Content as ExpResultsTable;
string selector = resultsTableControl.SelectedTabItem.Header.ToString();
if (selector.Equals("special")) {
DifferenceDataTemplateColumn col = new DifferenceDataTemplateColumn();
col.Header = e.PropertyName;
col.CellValue = e.PropertyName;
col.CellTemplate = (DataTemplate)FindResource("DifferenceDataTemplate");
col.CellTemplate.DataType = typeof(MyDatabaseObject);
col.IsReadOnly = true;
e.Column = col;
}
}
private void DataGrid_Calibs_LoadingRow(object sender, DataGridRowEventArgs e) {
MyDatabaseObject calib = e.Row.DataContext as MyDatabaseObject;
// anything I can do about the problem in here?
}
Вторият манипулатор на събития е тук най-вече, за да ви покаже, че наистина се свързвам с MyDatabaseObject
вместо с RowView
.
С готовност признавам, че първите четири реда са грозни, но поне засега работят. Всякакви предложения обаче са добре дошли.
Останалото също работи добре: „нормалните“ таблици се попълват, както е планирано, а „специалната“ таблица е форматирана с моя персонализиран StackPanels
във всяка клетка. Не успявам обаче да попълня тези клетки.
Въпросите
Ето върху какво размишлявам от дни и което не мога да разбера, задавайки запитвания към обичайните заподозрени (Stackoverflow, MSDN и т.н.):
- Как мога да получа достъп до съдържанието на клетката? Събитието
LoadingRow
е мястото, където трябва да получа достъп до низа, да го разделя и да задамFieldValue_Old.Text
иFieldValue_New.Text
. Мога да осъществя достъп до обекта чрезcalib
(вижте по-горе)—дебъгерът показва, че съм точно там—но за живота си не мога да разбера как да осъществя достъп доTextBlock
s на една („специална”) клетка, нека сам как да направя това за всички колони на реда под ръка. - Мога ли да разреша това с помощта на
CellTemplateSelector
? как? - Използвам ли правилния манипулатор на събития? Има ли събитие, което да предпочета пред тези?
- Има ли друг начин да се постигне този резултат? Например, мога ли да убедя
DataGrid
да интерпретира HTML в своите клетки? (Не е нужно да се тревожа за инжектиране на код тук, тъй като бих генерирал HTML код само в момента на попълване наDataGrid
.)
Всяка помощ е високо ценена и ще ме предпази от бързо приближаване до плешивост, докато си късам косата заради това. Благодаря ти!
Актуализация
Забравих да добавя кода за персонализирания DataGridTemplateColumn
, за който имах предвид в гореспоменатия манипулатор на събития AutoGeneratingColumn
.
public class DifferenceDataTemplateColumn : DataGridTemplateColumn {
public string CellValue { get; set; }
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
ContentPresenter presenter = (ContentPresenter)base.GenerateElement(cell, dataItem);
BindingOperations.SetBinding(presenter, ContentPresenter.ContentProperty, new Binding(this.CellValue));
return presenter;
}
}