dispay data matrix with different number of columns in each row - wpf

I need to display ,in a WPF app, a 'data matrix' that has no fixed tabular structure i.e.
data row 1: aaa bb ccc
data row 2: 222 $$ ddddd eeee
...
all data is in a list of arrays.
i need to show this data and enable a user to scroll through it.
the strange data structure comes from a textual flat file, and i cannot change this.
i have read a lot, and experimented with a list-view and a grid-view, but so far i am only able to get:
1. dymanic column creation, but this is no good since i have a different schema for each row
2. rendering each row of data as (delimited/formatted) text. but this is no good since all row data is endign up in a single cell (obviously).
3. (havn't done this yet, hoping to avoid) have lots of data templates with triggers, each with a diffrent column count - say from 5 to 40 - to acomodate vast majority of row types.
my question is: how do i bind this data to a list-like / grid-like scrollable view ?
Thanks in advance

If you don't need the ability to select an item, you can use nested ItemsControls
Simply bind the first ItemsControl to your collection of arrays, and set the ItemTemplate to another ItemsControl which binds to the array of values.
Something like this:
<ScrollViewer Height="300" Width="400">
<ItemsControl ItemsSource="{Binding ListOfArrays}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }" Width="50" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>

One pass to find the maximum number of columns.
Build a simple Row class that has a property cols string[maxColCount].
Build the maxColCount columns for a GridView in code bind and bind the source to cols[x].
The compiler needs a property but you can bind the column to an collection name index
Syntax for building and binding a GridViewColumn
gvc = new GridViewColumn();
gvch = new GridViewColumnHeader();
gvch.Content = fd.FieldDef.DispName;
gvch.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
if (fd.FieldDef.Sort)
{
gvch.Click += new RoutedEventHandler(SortClick);
gvch.Tag = fd.FieldDef.Name;
}
// if (fd.ID == 0 || fd.ID == 1) gvc.Width = 60; sID, sParID
if (!fd.AppliedDispGrid) gvc.Width = 0;
gvc.Header = gvch;
gvBinding = new Binding();
gvBinding.Mode = BindingMode.OneWay;
gvBinding.Path = new PropertyPath("DocFields[" + sDocBaseResultDocsFieldsIndex.ToString() + "].DispValueShort");
template = new DataTemplate();
textblock = new FrameworkElementFactory(typeof(TextBlock));
textblock.SetValue(TextBlock.TextProperty, gvBinding);
textblock.SetValue(TextBlock.TextTrimmingProperty, TextTrimming.WordEllipsis);
// <Setter Property="TextTrimming" Value="WordEllipsis" />
template.VisualTree = new FrameworkElementFactory(typeof(Grid));
template.VisualTree.AppendChild(textblock);
gvc.CellTemplate = template;
gvSearchResults.Columns.Add(gvc);

Related

WPF - Reuse templates with different bindings

Why not close this question?
Please don't close the question: the suggested link doesn't contain an answer, because there is no Binding property in DataGridTemplateColumn and I can't find a way to bind data to it.
It seems to be possible to use Text={Binding} inside the data template and this is "half" of the answer
Original question
I'm sort of a newbie in WPF.
I have a DataGrid where I want to have specific templates for some columns, but all the templates are the same.
For instance
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<!-- first column is a standard column, ok-->
<DataGridTextColumn Header="First Column" Binding="{Binding FirstColumn}"/>
<!-- a few of the other columns use a custom template-->
<DataGridTemplateColumn Header="Second Column">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding SecondColumn, UpdateSourceTrigger=PropertyChanged}"/
<OtherThings />
<OtherThings />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<More Columns Exactly like the above, with different bindings/>
<!-- Column3 Binding={Binding Column3}-->
<!-- Column4 Binding={Binding Column4}-->
</DataGrid.Columns>
</DataGrid>
How can I create a template as a static resource in a way that I can set the binding of this template?
I tried to create a static resource like
<DataTemplate x:Key="ColumnTemplate">
<TextBox Text={I have no idea what to put here}/>
<OtherThings/>
<OtherThings/>
<DataTemplate>
And use it like
<DataGrid>
<DataGrid.Columns>
<!-- Where does the Header go?, where does the binding go?-->
<!-- binds to column 2-->
<DataGridTemplateColumn CellTemplate="{StaticResource ColumnTemplate}">
<!-- binds to column 3-->
<DataGridTemplateColumn CellTemplate="{StaticResource ColumnTemplate}">
</DataGrid.Columns>
</DataGrid>
But I really don't know how to make the binding correctly from the item to the textbox inside the template.
How can I achieve this?
In principle, you can do this.
Whether this is a good plan or not is arguable though.
You can change the cell template to surround whatever the column binds to with other markup. Which seems to be what you want.
There are some subtelties to this in that the datagrid will switch out content of a cell from a textblock to textbox if you allow editing in the datagrid. Which is inadvisable for anything but trivial requirements - but that's perhaps a different subject.
In the datagrid or a parent resources:
<Window.Resources>
<Style x:Key="StringStyle" TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<StackPanel>
<ContentControl>
<ContentPresenter/>
</ContentControl>
<TextBlock Text="A constant string"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can then apply that to a column:
<DataGridTextColumn Header="Date" Binding="{Binding DT, StringFormat=\{0:dd:hh:mm:ss.FF\}}"
CellStyle="{StaticResource StringStyle}"
As I mentioned above.
Weird things are likely to happen when you click on the cell to edit it.
You'd probably also want code to focus on the textbox within the contentpresenter.
If you instead wanted to cut down on repeated code you could use xamlreader to build your column dynamically using a template.
If you just have maybe 3 columns are the same then this is likely overkill but it'd be largely copy paste.
I built a sample illustrating this:
https://social.technet.microsoft.com/wiki/contents/articles/28797.wpf-dynamic-xaml.aspx#Awkward_Bindings_Data
https://gallery.technet.microsoft.com/WPF-Dynamic-XAML-Awkward-41b0689f
The sample builds a moving selection of months data.
The relevent code is in mainwindow
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;
}
The col.txt file contains all the "standard" markup for a column. This is then manipulated as xml to replace values.
The result is then turned into wpf ui objects using xamlreader.parse
And again.
Probably way over engineered for a requirement has just a couple of columns.

Performance issues with CellTemplate of GridViewColumn

I am using a ListView with it's View set to GridView to display items in dynamically generated columns. The ListView's virtualization is enabled. For this example I want to display up to 10 rows with 50 columns (but the count of columns is dynamic).
The first thing I do is to create the columns in code behind in a loop.
"Items" is just an ObservableCollection of strings in my view model for one row.
int index = 0;
foreach(Header header in Headers)
{
GridViewColumn column = new GridViewColumn();
column.Header = // the view model of the header;
column.HeaderTemplate = Resources["HeaderTemplate"] as DataTemplate;
column.DisplayMemberBinding = new Binding("Items" + "[" + index + "]");
m_MyGridView.Columns.Add(column);
index++;
}
Now, when I change the ItemsSource of my ListView, the data gets updated quiet fast.
Unfortunately, each string in every cell has to be formatted (font color, foreground etc). So I cannot use DisplayMemberBinding, but CellTemplate to assign a DataTemplate to every cell.
For this purpose I create a DataTemplate in xaml in my control that holds the ListView. This contains just a TextBlock with some bindings to properties of the cell's view model to define the string style of the TextBlock.
Now, Items is not just a collection of string but a collection of CellViewModels that not only contain the value to display but also information of how to display it. This information is then bound inside the DataTemplate.
The CellTemplate is set up in code and assigned to each GridViewColumn's CellTemplate:
foreach(Header header in Headers)
{
GridViewColumn column = new GridViewColumn();
column.Header = // the view model of the header;
column.HeaderTemplate = Resources["HeaderTemplate"] as DataTemplate;
column.CellTemplate = createCellTemplate(index);
m_MyGridView.Columns.Add(column);
index++;
}
DataTemplate getCellTemplate(int index)
{
FrameworkElementFactory elementFactory = new FrameworkElementFactory (typeof (ContentControl));
elementFactory.SetBinding(ContentControl.ContentProperty, new Binding("Items" + "[" + index + "]"));
DataTemplate dt = Resources["ValueTemplate"] as DataTemplate;
elementFactory.SetValue(ContentControl.ContentTemplateProperty, dt);
DataTemplate dataTemplate = new DataTemplate ();
dataTemplate.VisualTree = elementFactory;
return dataTemplate;
}
When I do this, things get really slow when I'm changing the ItemsSource of the ListView. It now takes nearly a second to render the new cells each time I change the ItemsSource to display different data.
So is there a way to speed things up? I thought about having the formatted string already in my CellViewModel by using a Paragraph, but unfortunately DisplayMemberBinding only supports string, so I have to use the CellTemplate.
Edit: so, here is the listview i'm using:
<ListView ItemsSource="{Binding ElementName=Control, Path=ItemViewModels}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" VirtualizingStackPanel.IsVirtualizing="True" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.View>
<GridView x:Name="m_MyGridView"/>
</ListView.View>
</ListView>
and the DataTemplate for the cells:
<DataTemplate x:Key="ValueTemplate">
<Border Background="{Binding Path=BackgroundColor">
<TextBlock Text="{Binding Path=Value}"
Foreground="{Binding Path=ForegroundColor}"
FontStyle="{Binding Path=FontStyle}"
FontWeight="{Binding Path=FontWeight}"
</TextBlock>
</Border>
</DataTemplate>
The function getCellTemplate is called only once when the headers are created. When the ItemsSource changes, it is not called, because they have been created already.

Can DbDataAdapter create DataColumn objects incompatible with WPF binding?

I have a situation in which I query data from a database and fill a DataTable with it. Then Binding the DataTable to an ItemsControl is failing to update properly. The root of the problem seems somehow related to having pulled the data from a Firebird database, but I can't work out how that could possibly matter. Is it possible for an implementation of DbDataAdapter to create DataColumn objects that are incompatible with WPF binding?
Here's more detail. The ItemsControl is simple:
<ItemsControl ItemsSource="{Binding COEStudents}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions>
<TextBox Text="{Binding [Foo]}" Grid.Column="0"/>
<TextBox Text="{Binding [Foo]}" Grid.Column="1"/>
<TextBox Text="{Binding [LastName]}" Grid.Column="2"/>
<TextBox Text="{Binding [LastName]}" Grid.Column="3"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My Window.DataContext is set to an object with a property like this:
public DataView COEStudents { get { return coeStudTable.DefaultView; } }
The coeStudTable is populated like this:
FirebirdSql.Data.FirebirdClient.FbConnection conn=new FirebirdSql.Data.FirebirdClient.FbConnection(
#"dialect=3;charset=NONE;connection lifetime=0;connection timeout=15;pooling=True;packet size=8192;initial catalog=localhost:c:\\Users\\kdonn\\DBs\\NE\\MIS2000.fdb;server type=Default;user id=fubar;password=blah");
FirebirdSql.Data.FirebirdClient.FbDataAdapter adapter=
new FirebirdSql.Data.FirebirdClient.FbDataAdapter( "select first 3 LastName from Student", conn);
adapter.Fill(coeStudTable);
coeStudTable.Columns.Add("Foo", typeof(string));
foreach (DataRow r in coeStudTable.Rows)
r["Foo"]=r["LastName"];
The ItemsControl shows the three rows of data, four copies of each. When I change the value in Column 0 and tab away, the value in Column 1 shows the changed value as expected. When I change the value in Column 2 and tab away, the value in Column 3 does not change.
If I switch from Firebird to SQL Server for my data as follows, everything works fine:
System.Data.SqlClient.SqlDataAdapter adapter=
new System.Data.SqlClient.SqlDataAdapter( "select top 3 LastName from HRRM",
#"Data Source=localhost\SQLEXPRESS;Initial Catalog=Viewpoint2;Integrated Security=True");
This seems to suggest that somehow the FbDataAdapter is creating DataColumn objects that don't play nice with WPF data binding, but how could that be?

WPF creating dynamic rows using MVVM pattern

I want to load my WPF UserControl with dynamic rows. My scenario is as follows.
1. When the UserControl gets loaded, I will populate my List<string> object with some values which I will get from a database.
2. I need to create equal number of rows in my UserControl which matches the number of items in the List<string> object. The display will look something like below
Column 1 is a Label Control and Column 2 will be a TextBlock control
Label 1: Item 1 (This is the value from the List<string> object)
Label 2: Item 2
Label 3: Item 3
I know how to create rows dynamically but my problem is how do I do this when I'm using the MVVM pattern.
Note: I'm using the MVVM toolkit from CodePlex.
Thanks,
Jithu
Set the MVVM object you have as the dataContext of your UserControl, I hope the object has a Collection property in it. Then create an ItemsControl more like below
It is not clear from your description that where is really the Label and Item comes from your ViewModel. The below code will create Rows dynamically as many as your Collection.Count.
<ItemsControl ItemsSource="{Binding YourStringCollection}" HorizontalAlignment="Left" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
</DataTemplate >
</ItemsControl. ItemsTemplate >
</ItemsControl>

Programmatically create WPF DataGridTemplateColumn for DataGrid

I would like to be able to programmatically create DataGridTemplateColumns based on my data source. For example, if my source has a date in a particular column I would like to be able to utilize a Datepicker control. I know this is easily accomplished with xaml and a DataGridTemplateColumn at design-time, however, how would I accomplish this at run-time?
Is my best option xamlreader.load or a more traditional route like:
Dim TempCol As Microsoft.Windows.Controls.DataGridTemplateColumn
I have not had any success with the latter.
Thanks.
-Paul
Edit:
This is the code I attempted to use:
Dim TempCol As New Microsoft.Windows.Controls.DataGridTemplateColumn
TempCol.CellEditingTemplate = DataTemplate.Equals(DatePicker)
I receive DatePicker is a type and cannot be used as an expression.
I am basiing this on the WPF Toolkit demo.
http://windowsclient.net/wpf/wpf35/wpf-35sp1-toolkit-datagrid-feature-walkthrough.aspx
<dg:DataGridTemplateColumn Header="Date" MinWidth="100">
<dg:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<dg:DatePicker SelectedDate="{Binding Date}" SelectedDateFormat="Short" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellEditingTemplate>
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Date, StringFormat=d}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
Thanks!
The reason that your code does not work is because you are setting the value of the CellEditingTemplate column to a bool (the result of calling DataTemplate.Equals(), rather than creating an instance of the template in code.
You can create a template in code using something like this (equivalent to the XAML code snippet you provided):
DataGridTemplateColumn col = new DataGridTemplateColumn();
col.Header = "Date";
// Create a factory. This will create the controls in each cell of this
// column as needed.
FrameworkElementFactory factory =
new FrameworkElementFactory(typeof(DatePicker));
// Bind the value of this cell to the value of the Date property of the
// DataContext of this row. The StringFormat "d" will be used to display
// the value.
Binding b = new Binding("Date");
b.StringFormat = "d";
factory.SetValue(DatePicker.SelectedDateProperty, b);
// Create the template itself, and add the factory to it.
DataTemplate cellEditingTemplate = new DataTemplate();
cellEditingTemplate.VisualTree = factory;
col.CellEditingTemplate = cellEditingTemplate;
I'm not sure if this approach would work better than loading the XAML yourself. Maybe try both approaches and see which one works best for you, and works faster?

Resources