Synchronising TableLayoutPanels - c#

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;
}
}

Related

How to sum 2 columns within a row of datagridView

Hello guys? i'am working on Windows application form c# and I have 4 Columns in my datagridView, lets say i have 3 rows just like this
-------------------------------
|Grade1|Grade2|Average|Remarks|
|------|------|-------|-------|
| 85| 80| 82.5|PASSED |
| 76| 86| 81|PASSED |
| 75| 72| 73.5|FAILED |
-------------------------------
Now my question is it possible if i click a compute button it will compute each rows of the Column"Grade1" and Column"Grade2" the computation is
Grade1 + Grade2 = Average and Average = Average/2
if it's below 74 the remarks column automatically Have a "FAILED"
if it's above 75 the remarks column automatically Have a "PASSED"
Does anyone have a example code or a link that can help me to do this? Thank you so much!
you can enumerate thru all the rows by doing
foreach(DataGridViewRow row in dataGridView.Rows)
you can get or set the value of cells in that particular row by doing
row["column name"].Value
you can attach a click event to Button.Click (either in code by using .Click += handler or do it in the designer)
To assemble all those pieces and come up with a working solution will be your exercise. Good luck :)
private void CalculateButton_Click(object sender, EventArgs e)
{
foreach (DataGridViewRow row in dgvGrades.Rows)
{
if (decimal.TryParse(row.Cells["Grade1"]?.Value?.ToString(), out decimal Grade1)
&& decimal.TryParse(row.Cells["Grade2"]?.Value?.ToString(), out decimal Grade2))
{
var avg = (Grade1 + Grade2) / 2;
row.Cells["Average"].Value = avg;
if (avg < 74)
{
row.Cells["Remarks"].Value = "FAILED";
}
else
{
row.Cells["Remarks"].Value = "PASSED";
}
}
}
}
I would just add members to the class that is populating the array or list that is used to populate the grid. The getters for the average and pass/fail fields can handle the logic.

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.

C# Gtk get rendered size

In Gtk# (using Mono), I sometimes need to allocate the default size to a widget (I understand you do that by widget.SetSizeRequest(-1, -1);. Someone please correct me if this is not the proper way). I do this to ensure that the size of a, say, Button widget is just as expected. I don't want it to be too big or too small. Allocating the default size ensures that the button is not too big but not too small either.
So when I allocate the default size to a widget, the widget.GetSizeRequest(out width, out height); returns -1 and -1 for width and height respectively.
Which is expected but not what I need. I need to know the rendered size (width and height separately) as I need to place another widget relative to it. So unless I know about its size, I can't place the other widget in the right position.
Although there are other instances where knowing about the rendered size would be helpful, this is the main reason.
I hope you understand what I mean by rendered size.
Update:
Since the answers seem to suggest using widget.Allocation although I've tried (see my comment), here's what I've tried:
Widget[] widgets = GetWidgets();//I initialize the widgets somewhere and get them as an array
//fix is a Gtk.Fixed container
foreach(Widget widget in widgets)
{
//although this is not the exact way I place the widgets, this is just an example
fix.Put(widget, GetPosition(widget).X, GetPosition(widget).Y);
widget.SetSizeRequest(-1, -1);
Console.WriteLine(w.Allocation.Size);
}
//later on, I need to place a few widgets relative to another
The output of this code is:
{Width=1, Height=1}
{Width=1, Height=1}
{Width=1, Height=1}
{Width=1, Height=1}
{Width=1, Height=1}
{Width=1, Height=1}
However, when I print the Allocation of a, say, Button widget in the Clicked event of the Button, it prints the rendered size as expected. However, in the above scenario, it just prints 1 and 1.
Can anyone identify what I'm doing wrong?
You are looking the Allocation property (get_allocation in gtk).
Example:
protected void OnButton2Clicked (object sender, EventArgs e)
{
var rect = button.Allocation;
PropertyInfo[] properties = rect.GetType().GetProperties();
var sb = new System.Text.StringBuilder();
foreach (PropertyInfo pi in properties)
{
sb.Append(
string.Format("Name: {0} | Value: {1}\n",
pi.Name,
pi.GetValue(rect, null)
)
);
}
Console.WriteLine (sb);
}
Output:
Name: GType | Value: GdkRectangle
Name: Top | Value: 50
Name: Bottom | Value: 82
Name: Right | Value: 69
Name: Left | Value: 30
Name: IsEmpty | Value: False
Name: Size | Value: {Width=40, Height=33}
Name: Location | Value: (30,50)
Update:
A widget that has not been rendered (this includes Visible=false) does not have any container allocation, as, well it does not need any and thus packing will occur correctly for the rest of the visible widgets.
Name: GType | Value: GdkRectangle
Name: Top | Value: -1
Name: Bottom | Value: -1
Name: Right | Value: -1
Name: Left | Value: -1
Name: IsEmpty | Value: False
Name: Size | Value: {Width=1, Height=1}
Name: Location | Value: (-1,-1)
If you really need a true rendered size (with current fonts, border, etc...), you need to render it somewhere. A real hack would be to place it on a double-buffered fixed container and 'render' it but never display it. Get and save the allocation from it (maybe in a dictionary), delete the widget and do it for the next one. Now you have a dictionary of widget sizes that match the current runtime environment..., like I said, a real hack...
Also asking this question a Gtk tagged C language question, or on their mailing list, might get you a better answer, their answer would be in C vs C#, transform it as needed. Make sure you mention that you need to do this in Gtk2 as Gtk3 is really different.
What you're looking for is in widget.Allocation, however the size is only set at the Expose Event. That gives you the rendered size of the widget. I'm guessing you're also using a Fixed container though. widget.SetSizeRequest(-1, -1) clears any programmed sizing and lets the Gtk library do what it wants with the size. Unless you really need tight control where each widget is placed, you're best off using containers such as VBox and HBox. One of the advantages of Gtk is that it does a lot of the finer alignment of widgets for you.
EDIT:
You could just draw one widget at a time. Its sort of, kind of "recursion". Once the Gtk run time calls the expose event handler, the size of the widget has already been determined so you can use that to place the next widget. Here is a really simplified example that draws three buttons at a downward diagonal line.
int idx;
string[] userDefinedText;
Fixed fix;
public MainWindow () : base (Gtk.WindowType.Toplevel) {
Build ();
// I'm not really sure how you store the text or just a list of widgets
// but for the sake of simplicity I'm just using a string array
idx = 0;
userDefinedText = new string[3] { "Medium text length", "Long text that goes on and on", "Short" };
fix = new Fixed ();
fix.SetSizeRequest (400, 400);
Add (fix);
fix.Show ();
Button b = new Button ();
b.SetSizeRequest (-1, -1);
b.Label = userDefinedText [idx];
b.ExposeEvent += OnButtonExposeEvent;
fix.Put (b, 5, 5);
b.Show ();
ShowAll ();
}
protected void OnButtonExposeEvent (object sender, ExposeEventArgs args) {
if (idx < (userDefinedText.Length - 1)) {
// There are still widgets to be placed on the screen
++idx;
Button b = sender as Button;
int x = b.Allocation.Right;
int y = b.Allocation.Bottom;
Button b2 = new Button ();
b2.SetSizeRequest (-1, -1);
b2.Label = userDefinedText [idx];
b2.ExposeEvent += OnButtonExposeEvent;
fix.Put (b2, x + 5, y + 5);
b2.Show ();
}
}
One last note, and only if you're using GTK+ 3:
I really don't know what you're trying to get the sizes for, buut if you're implementing your own container and want to see what size the widget wants to be (in a way better than RobertN's "real hack") is to use the preferred size system. GTK+ 3 has methods such as get_preferred_width() and get_preferred_height(), which return both a minimum (the absolute minimum, so just the width of ... for a label, for example) and a natural (the more reasonable size that the widget prefers to be to keep every part of it visible) size that you can use; see which one fits your use case more. There's also equivalents for height-for-width and width-for-height geometry (if you ever decide to do that).
I do not know what these methods are called in C#.

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.

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