c# wpf datagrid merge cells - c#

I'm trying to make DataGrid look like:
--------------------------------------
Point name | Distance between points|
--------------------------------------
pt1 |------------------------|
------------| 10.45 |
pt2 |------------------------|
------------| 8.55 |
pt4 |------------------------|
------------| 4.89 |
pt5 |------------------------|
------------| |
The grid is going to be binded to BindingList.
BindingList<PointClass> points;
class PointClass
{
public string Name { get; set;}
public double DistToNextPoint { get; set;}
}
Is it even possible with datagrid? Or should I make a custom control?

The DataGrid is an ItemsControl adapted to represent cells in a row aligned in columns. But the basis is the same - the representation of the source elements (ItemsSource). In the case of the DataGrid, the representation is only row-by-row. Since you need an interline representation, the DataGrid is not suitable for this.
You need either very deep intervention in the DatGrid template, or creating your own CustomControl (possibly derived from ItemsControl).

Related

Is there a way to create smth like a Scenario Outline in Gherkin that is executed for multiple values without restarting the Scenario?

I am a test designer and I am creating .feature files.
We are using Visual Studio + Specflow, the code is written in C#.
I have a following test case:
Background:
Given something
And something
And something
Scenario: Scenario name
When I set the 'X' value' in the Y field
Then The 'X' value is displayed in 'Somewhere'
My problem is:
I need to check 20 values.
But if I use Scenario Outline with Examples, then it will be run from the start for each 'X' value I use, all Givens will be executed everytime.
This will take long while the test is quite simple and running the When+Then combination for each value after the background is run could be enough for the test purpose.
Is there a way to check multiple values in Gherkin without using the Scenario Outlines, having a possibility to just run a When and Then combination multiple times to check each value?
Appreciate any help with this matter
You could use a datatable as input, and implement your step in such a way that it parses the datatable. You can then perform assertions on each table element.
SpecFlow DataTables Documentation
Given I test the following outcomes
| value | field | display_location |
| X | Y1 | Somewhere |
| y | Y2 | Somewhere Els |
By having an instance variable to hold the test values, you can perform actions and verifications in other steps on the dataset.
You have two steps - one to set some value to the field, and another to check if some field has given value:
[When(#"I set the '(.*)' value in the (.*) field")]
public void WhenISetTheValueInTheField(string value, string field)
{
// ...
}
[Then(#"The '(.*)' value is displayed in (.*) field")]
public void ThenTheValueIsDisplayedInField(string value, string field)
{
// ...
}
With Specflow you can call steps from Step Definitions. So basically you need a high-level step which will explain your high-level intent - verification that all given input values are displayed correctly. To pass values and verification data you can use a table:
Then all input values are displayed correctly:
| InputValue | InputField | DisplayedValue | DisplayField |
| X | Y | X | Somewhere |
| Z | Y | Z | SomewhereElse |
And inside this step, you can call steps which you already have for each set of data:
[Then(#"all input values are displayed correctly:")]
public void ThenAllInputValuesAreDisplayedCorrectly(Table table)
{
foreach (var row in table.Rows)
{
var inputValue = row["InputValue"];
var inputField = row["InputField"];
When($"I set the '{inputValue}' value in the {inputField} field");
var displayedValue = row["DisplayedValue"];
var displayField = row["DisplayField"];
Then($"The '{displayedValue}' value is displayed in {displayField} field");
}
}
The Nice thing here is that beside high-level step, you will see each step with its parameters in the output.

How to create a grid

I would like to create a grid, with cells(I believe this is the correct term for this, is it?)
Like this(take z,x,c,v,b as my "cells"):
z|x|c|v|b|x|c|b|b
-----------------
z|x|c|v|b|x|c|b|b
-----------------
z|x|c|v|b|x|c|b|b
-----------------
z|x|c|v|b|x|c|b|b
-----------------
z|x|c|v|b|x|c|b|b
-----------------
z|x|c|v|b|x|c|b|b
The order of the "cells" is random, so dont worry about that, also the lines are only there to help you visualize what i mean
What you are looking for are Arrays
To make a grid specificly, you need a 2D array. For this example I will use a string as the data, but you can use any class you want.
string[,] cells = new string[width, height];
You can then access and set the values of these cells like so:
cells[x, y] = "z";
string something = cells[x, y];

Creating a multi-layered matrix-ish Collection in C#

The setup
I have a List<Room>() which I get back from a service. The list refreshes every 10 seconds, and rooms get added and removed.
class Room
{
public int ID {get;set;}
}
My job
To display these rooms on the screen, I have a Matrix-like view of variable size.
Sometimes the matrix is 3 x 3 cells, other times it is 4 x 2 or 5 x 1.
I needed a way to "remember" which slot/cell a room has been placed in, so I thought a DataTable would give me that option.
To store the cells I use a DataTable, which has 3 Columns:
"Column" (int)
"Row" (int)
"Room" (Room)
So If I have a 2 x 4 matrix, it would look like this.
Column | Row | Room
-----------------------------
0 | 0 | rooms[0]
-----------------------------
1 | 0 | rooms[1]
-----------------------------
2 | 0 | rooms[2]
-----------------------------
0 | 1 | rooms[3]
-----------------------------
1 | 2 | rooms[4]
And so forth...
Once I have this DataTable I am then able to refresh the screen, knowing that each room will be displayed at the position it was before. This can probably be achieved in a smarter way.
The problem
Now I need to enumerate the List<Room> and fill the matrix/DataTable.
If I have more rooms than cells, then I need to start at position 0,0 again (like adding a new matrix as a layer), until all rooms have been assigned a cell.
The approach so far
I have tried a few for(...) loops that look like:
int totalTiles = area.TileColumns * area.TileRows;
int totalLayers = (int)Math.Ceiling((double)area.Rooms.Count / totalTiles);
for (int i = 0; i < totalLayers; i++)
{
for (int j = 0; j < area.TileRows; j++)
{
for (int k = 0; k < area.TileColumns; k++)
{
// This is going nowhere :-(
}
}
}
In my brain
When I first came across this problem, I immediately thought: "Nothing a simple LINQ query won't fix!". And then I bricked ...
What would be the most efficient / best performing approach to fill this matrix?
Without being able to make assumptions, like will the row/columns change at runtime, I would have to say just make it completely dynamic.
class RoomStorage
{
public Room room {get;set;}
public int layer {get;set;}
public int row {get;set;}
public int col {get;set;}
}
var matrix=new List<RoomStorage>();
Then you can things like:
var newRooms=new List<Room>(); // Get from service
//Remove rooms no longer in use
var matrix=matrix.Where(m=>newRooms.Select(nr=>nr.ID).Contains(m.Room.ID));
//Find rooms we need to add (Optionally use Exclude for faster perf)
var roomsToAdd=newRooms.Where(r=>matrix.Select(m=>m.Room.ID).Contains(r.ID));
var maxLayer=matrix.Max(m=>m.layer);
var rows = ?
var cols = ?
var positions=Enumerable
.Range(0,maxLayer+1)
.SelectMany(layer=>
Enumerable
.Range(0,rows)
.SelectMany(row=>
Enumerable
.Range(0,cols)
.Select(col=>new {layer,row,col})));
Then you can use positions, left joining it to matrix for display purposes, or finding the first empty position.

Synchronising TableLayoutPanels

I have several TableLayoutPanels, each of which displays a category of name/value information in two columns - one with informational labels, and one with data labels.
In each one, I've set the first column to autosize and right-aligned all the labels, which works just fine. However, it works separately to each TableLayoutPanel (obviously), and looks something like this:
TableLayoutPanel 1:
+--------+--------+
| Name 1 | Data 1 |
+--------+--------+
| Name 2 | Data 2 |
+--------+--------+
| Name 3 | Data 3 |
+--------+--------+
TableLayoutPanel 2:
+------------------+--------+
| Long Name 1 | Data 1 |
+------------------+--------+
| Very Long Name 2 | Data 2 |
+------------------+--------+
| Long Name 3 | Data 3 |
+------------------+--------+
I'm looking for a way to consider all of the name labels when autosizing all of the first columns, so it looks like this:
TableLayoutPanel 1:
+------------------+--------+
| Name 1 | Data 1 |
+------------------+--------+
| Name 2 | Data 2 |
+------------------+--------+
| Name 3 | Data 3 |
+------------------+--------+
TableLayoutPanel 2:
+------------------+--------+
| Long Name 1 | Data 1 |
+------------------+--------+
| Very Long Name 2 | Data 2 |
+------------------+--------+
| Long Name 3 | Data 3 |
+------------------+--------+
I can't put all the data into one table, because each table represents a different category of information, and is inside a custom control with a collection of collapsible panels (so you can show or hide each category separately).
I've been trying to achieve this by overriding the container controls OnLayout(), setting all of the TableLayoutPanels' first columns to autosize, getting all of their widths, finding the maximum, and then settings all of their first columns to a fixed size of the greatest width. This works, but looks horrible every time layout occurs as all the columns jump to autosize and then back to fixed size.
I'm assuming I'm going to have to hook ControlAdded and ControlRemoved for each table, and then SizeChanged for each child control, to know when the size of any child control changed, and then manually set the column width somehow, but I'm not sure how to reliably get the correct widths.
I tried a variation of the first method - using GetPreferredSize() on all the controls in the first columns, to attempt to find the largest width, and then setting all first columns to a fixed size, but it seemed to return widths that were slightly to small. Should I be applying some extra spacing?
Does anyone know any way of asking the TableLayoutPanel to perform autosize calculations without it actually applying them visually? Or perhaps, lying to the tables to 'pretend' that there is a control of a certain width, just so it takes it into account? I can't add actual controls, since it will then want to allocate more cells for them. I tried looking at the source with ILSpy, but well, it isn't pretty. Seems most of the work is done by TableLayout class, which is, of course, internal, and I couldn't follow what it was doing.
Thanks for any ideas...
You can use the Graphics.Measurestring to determine the length in pixels without actually drawing it. There are some slight imperfections with it, so you may think about adding or removing some padding. After a test or two, you can get pretty close. That's as proper of a way as I know of, and it doesn't involve the text being in a label.
Also, trying to find a way to get the TableLayoutPanel to calculate sizes without displaying it visually just sounds like you're trying to hack it into doing something it wasn't designed to.
It turned out the width returned by GetPreferredSize() was useful, it was just 'too late'; I was working out the correct size and returning it within code that was called from the TableLayoutPanels' OnLayout() method, and setting the column width there has no effect until the next layout.
I had a solution that used a separate Component implementing IExtenderProvider which could be used to join tables together, but due to the issue above it always lagged behind control changes. Even hooking SizeChanged on all the child controls, the TableLayoutPanel gets the event first, and starts layout.
So I couldn't see any other way but to override the layout process itself. I tried creating a LayoutEngine that performed the necessary calculations, resized the columns, then delegated the actual layout work to the old layout engine, but unfortunately Control.LayoutEngine is read-only, and TableLayoutPanel doesn't even use a backing field, it just returns another object directly, so I couldn't even get around it by using reflection to assign the private backing field.
In the end I had to resort to subclassing the control, to override OnLayout(). Here is the result:
public class SynchronizedTableLayoutPanel : TableLayoutPanel
{
/// <summary>
/// Specifies a key used to group <see cref="SynchronizedTableLayoutPanel"/>s together.
/// </summary>
public String SynchronizationKey
{
get { return _SynchronizationKey; }
set
{
if (!String.IsNullOrEmpty(_SynchronizationKey))
RemoveSyncTarget(this);
_SynchronizationKey = value;
if (!String.IsNullOrEmpty(value))
AddSyncTarget(this);
}
} private String _SynchronizationKey;
#region OnLayout(), Recalculate()
protected override void OnLayout(LayoutEventArgs levent)
{
if (ColumnCount > 0 && !String.IsNullOrEmpty(SynchronizationKey))
{
Recalculate();
ColumnStyles[0] = new ColumnStyle(SizeType.Absolute, GetMaxWidth(SynchronizationKey));
}
base.OnLayout(levent);
}
public void Recalculate()
{
var LargestWidth = Enumerable.Range(0, RowCount)
.Select(i => GetControlFromPosition(0, i))
.Where(c => c != null)
.Select(c => (Int32?)((c.AutoSize ? c.GetPreferredSize(new Size(Width, 0)).Width : c.Width)+ c.Margin.Horizontal))
.Max();
SetMaxWidth(this, LargestWidth.GetValueOrDefault(0));
}
#endregion
#region (Static) Data, cctor, AddSyncTarget(), RemoveSyncTarget(), SetMaxWidth(), GetMaxWidth()
private static readonly Dictionary<SynchronizedTableLayoutPanel, Int32> Data;
static SynchronizedTableLayoutPanel()
{
Data = new Dictionary<SynchronizedTableLayoutPanel, Int32>();
}
private static void AddSyncTarget(SynchronizedTableLayoutPanel table)
{
Data.Add(table, 0);
}
private static void RemoveSyncTarget(SynchronizedTableLayoutPanel table)
{
Data.Remove(table);
}
private static void SetMaxWidth(SynchronizedTableLayoutPanel table, Int32 width)
{
Data[table] = width;
foreach (var pair in Data.ToArray())
if (pair.Key.SynchronizationKey == table.SynchronizationKey && pair.Value != width)
pair.Key.PerformLayout();
}
private static Int32 GetMaxWidth(String key)
{
var MaxWidth = Data
.Where(p => p.Key.SynchronizationKey == key)
.Max(p => (Int32?) p.Value);
return MaxWidth.GetValueOrDefault(0);
}
#endregion
}
This version only cares about the first column, but it could be adapted to synchronise other columns, or rows.
This approach does not flicker or cause jumps with sizing:
public partial class Form1 : Form
{
private readonly Timer _timer = new Timer();
public Form1()
{
InitializeComponent();
_timer.Interval = 500;
_timer.Tick += (o, ea) => UpdateWithRandomSizes();
_timer.Start();
}
private void UpdateWithRandomSizes()
{
var rand = new Random();
label1.Text = new string('A', rand.Next(10));
label2.Text = new string('B', rand.Next(10));
label3.Text = new string('C', rand.Next(10));
label4.Text = new string('D', rand.Next(10));
tableLayoutPanel1.ColumnStyles[0].SizeType = SizeType.AutoSize;
tableLayoutPanel2.ColumnStyles[0].SizeType = SizeType.AutoSize;
var width1 = tableLayoutPanel1.GetColumnWidths()[0];
var width2 = tableLayoutPanel2.GetColumnWidths()[0];
var max = Math.Max(width1, width2);
tableLayoutPanel1.ColumnStyles[0].Width = max;
tableLayoutPanel1.ColumnStyles[0].SizeType = SizeType.Absolute;
tableLayoutPanel2.ColumnStyles[0].Width = max;
tableLayoutPanel2.ColumnStyles[0].SizeType = SizeType.Absolute;
}
}

How to save data and display it when I have some constraints

Its hard to specify a title...
I breaking my head to figure out this problem,
I have packet that including "data" "time" and "id".
I need to save it in some data structure and display only the data in some way on the form (maybe a datagridview),
but I need the ability to get the rest packet info (time and id) when I'm clicking on the displayed data.
For example:
0110 1110 0101 0001
The first data id is 9 and the time is 2222. When I click on the first data (0110) I need to display (lets say in lable on the form) id = 9 and time = 2222.
One more thing, the data must be displayed like the way in the above example (in row with space between the data.
Edit:
I forgot something important.
If I use databinding there is option to change the data location on the grid (based on some packet info) from some cell/row to another? if not maybe databinding its not good here.
If I understand what you are trying to do, using a DataGridView, try this:
// DataGridView
Databing the data source to the DataGridView (use a list of your packet for that)
---------------------------------
| DATA 1 | DATA 2 | DATA 3 | ... (Header)
---------------------------------
| 0110 | 1110 | 0101 | ... (Data)
---------------------------------
Add a event handler to the DataGridView CellContentClick, just like this:
private void myDataGrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex > -1 && e.ColumnIndex > -1) // A row and cell was selected
{
var packet = myDataGrid.Rows[e.RowIndex].DataBoundItem as Packet;
if (packet != null)
{
// Display packet information
}
}
}
Hope it helps.
Make your packet a class where data, time and id are private members and DisplayData is a public property. To access the content of the private members from outside, use properties with the [Browsable(false)] attribute:
public class Packet
{
private int data, time, id;
public string DisplayData {get {return FunctionToFormatDataToMyNeeds(data); }}
// ...
[Browsable(false)]
public int Time{get{return time;}}
}
Bind a list of those objects to the data source of your DataGridView.

Categories

Resources