How to force ultragrid column formula refresh? - c#

I use a numeric updown control to allow the users to adjust numeric values to effect a change in my ultragrid. In my ultragrid InitializeLayout event I have the following:
...
var band = e.Layout.Bands[0];
band.Columns["CalcMarkup"].Formula = "if ([Markup] > 0, [FinalCost]*([Markup]/100), 0)";
band.Columns["RemainingCost"].Formula = "if([CalcMarkup] > 0, 0, [FinalCost] )";
band.Columns["ForcedMarkup"].Formula = "if([RemainingCost] = 0, 0, ([RemainingCost]/[sumRemCost()])*[force()])";
...
grdMain.DisplayLayout.Bands[0].Summaries.Add("force", "[ForcedAdj()]", SummaryPosition.UseSummaryPositionColumn, grdMain.DisplayLayout.Bands[0].Columns["ForcedMarkup"]);
...
var value = 1 - (nudMarginPer.Value/100);
_marginFormula = string.Format("( ( ([finCost()]/{0})-sum([FinalCost]) )-sum([CalcMarkup])-5000000 )", "{0}");
band.Summaries.Add("ForcedAdj", string.Format(_marginFormula, value));
band.Summaries["ForcedAdj"].SummaryDisplayArea = SummaryDisplayAreas.None;
...
grdMain.DisplayLayout.Bands[0].Summaries.Add("force", "[ForcedAdj()]", SummaryPosition.UseSummaryPositionColumn, grdMain.DisplayLayout.Bands[0].Columns["ForcedMarkup"]);
in the numeric updown event:
void nudMarginPer_ValueChanged(object sender, EventArgs e)
{
var value = 1 - (nudMarginPer.Value/100);
grdMain.DisplayLayout.Bands[0].Summaries["ForcedAdj"].Formula = string.Format(_marginFormula, value);
}
This works well in that when the user presses the updown buttons the summary (ForceAdj) values change and is reflected in the grid summary row. However the column formula for ForcedMarkup doesnt change until a second button press. So now the summary is correct however the value in the cell is off.
My question is this... How do I force the formula for ForcedMarkup (or any column formula) to refresh to reflect the current calcs?
I have tried forcing a grid update, calcmanager recalc, etc... nothing seems to work.
*** 7/20: This appears to be an issue when using group by.

From my experience, it could be calcmanager's trick.
Calcmanager recalc() method forces recalculation only for dirtied formulas, so you should dirty them first.
Also, if DeferredCalculationsEnabled property is set to true, calcmanager may defer recalculation until formula cells (or whole grid) becomes visible.
That's what worked for me :
1. If datagrid is not visdible yet
e.g trying to get values of calculated columns/summaries while still in form constructor
calcmanager_instance.DeferredCalculationsEnabled = false;
calcmanager_instance.ReCalc();
2.If datagrid is already visible, but formulas need to be recalculated
calcmanager_instance.DirtyAllFormulas();
calcmanager_instance.ReCalc();
In general, this combination should unconditionally and instantly force recalculation of all formulas :
calcmanager_instance.DeferredCalculationsEnabled = false;
calcmanager_instance.DirtyAllFormulas();
calcmanager_instance.ReCalc();
I think, there must be a way to dirty only selected formulas, but I didn't look for it.

Related

C# WPF I have a dynamic grid of labels set up based off of a database. I need to set them up for drag and drop

I am fairly new to both C# and WPF, but the project I am working on seems to be a great fit for them. I have a dynamic grid of labels set up based off of a database. I need to set them up for drag and drop. I have some labels set up as header, and they are populated based off the next seven fridays. I made a function to pull the data from the database and compare each date to the content of the header label content. Then I make labels containing a job number and state for each of the dates that match with the header.
Kind of like this on load
content = Job# + " " + JobState
-Date+7n----Date+7n----Date+7n----Date+7n----Date+7n----Date+7n-----Date+7n
Content------Content-----Content-----Content-----Content-----Content------Content
Content------Content-----Content-----Content-----Content-----Content------Content
Content------Content-----Content--------------------Content-----Content------Content
Content---------------------Content--------------------Content-----Content------Content
Content---------------------Content--------------------Content---------------------Content
Pretty much when the date runs out of jobs it just doesn't make anymore labels for that column.
I tried to make it as dynamic and expandable as possible so I have a forward and backwards button that push all the dates up a week or back a week then repopulates the grid based off of the new date at the top.
private void AddLabel(ref int rowNum, string val, List<string>[] datGrid)
{
int rowLen;
int margin1;
int margin2;
var lb = new Label();
lb.HorizontalAlignment = HorizontalAlignment.Left;
lb.VerticalAlignment = VerticalAlignment.Top;
lb.Height = 32;
lb.Width = 155;
rowLen = datGrid[rowNum].Count;
lb.AllowDrop = true;
margin1 = 25 + (200 * (rowNum));
margin2 = 10 + (40 * (rowLen));
var gName = "grid" + (rowNum + 1) + (rowLen + 1);
lb.Margin = new Thickness(margin1, margin2, 0, 0);
lb.Content = val;
lb.Name = gName;
dyGrid.Children.Add(lb);
}
Where I put the row number that the label needs to be added to as rowNum, the actual content of the label as val and datGrid as the array of lists that holds the names for all the labels. I just can't figure out how to add a mouseDown or other drag and drop events to the labels as I make it.
The syntax you are looking for is
lb.MouseDown += new MouseButtonEventHandler(lb_MouseDown);
...
void lb_MouseDown(object sender, MouseButtonEventArgs e)
{
// Handle MouseDown event here.
Label lb = sender as Label;
}
But that said, I would recommend rethinking your design.
From what I can guess from your code, it sounds like you have a Grid (which allows it's children to overlap), and you are placing all the labels in the first cell of the Grid, and using the Margin property to position them. Its not a very efficient design, and will likely cause you problems in the future.
I would instead switch to using an ItemsControl bound to your collection of items, and drawing it's ItemsPanelTemplate as a Grid and your ItemTemplate as the Label. Then you can create data items so they represent the actual data item being represented by that item. Check out this similar answer if you want an example for what I am talking about.
Also if you do this, look up Bea Stollnitz's code for dragging/dropping databound items. Or some variation of it... I'm sure there's a copy of the code somewhere in my answer history too if you have questions.

Event to NumericUpDown for only user interaction

I have 3 NumericUpDown elements in my form. This elements is synchronized by their sum. For example sum is 9 elements values is 3,3,3 and increment is 2. When user is changed first element up from 3 to 5 we must get 5,2,2.
For synchronized I had tried to use events ValueChanged and VisibleChanged, but they working when we have a programmatic modification or user interaction.
I used this method for every element, but for this events this method starts changing again, when result values other elements is changing in a code.
private void numericUpDown1Change(object sender, EventArgs e)
{
int oldValue = Sum - (int)numericUpDown2.Value - (int)numericUpDown3.Value;
int average;
if ((int)numericUpDown1.Value - oldValue > 0)
{
average = ((int)numericUpDown1.Value - oldValue) / 2;
numericUpDown2.Value = numericUpDown2.Value - average;
numericUpDown3.Value = numericUpDown3.Value - average;
}
else
{
average = (oldValue - (int)numericUpDown1.Value) / 2;
numericUpDown2.Value = numericUpDown2.Value + average;
numericUpDown3.Value = numericUpDown3.Value + average;
}
}
I want to use event, what worked just when user clicking the up or down button, or by the user entering a new value.
What event I must choose for it?
Use the ValueChanged event, but keep a flag telling you if the change is done by code or by user.
For a single control you can keep a Boolean variable at the class level (I would probably call it IsValueChangedByCode) and set it to false. Inside your methods, right before you change the value, set it to true and right after that back to false. Inside the event handler check that flag is true or false, and decide if you want to execute the code or not.
For 3 controls the logic is the same, but instead of using a boolean flag, you need 2 different booleans, or you can use an integer or flags enum.

TableLayoutPanel Winforms not showing all information

I have a really strange set of circumstances that I just can't seem to get to work. I will let you know what I have and see If you can put me right. (The below represents the closest I have been able to get to what I want).
The idea is that when a day is selected I show a usercontrol that has a lorry's deliveries for that day.
The thing is that the date may be a range. Therefore I have the following setup thus far:
I have a
TableLayoutPanel (Dock = Fill; 1 column (100percent); 1 Row (Autosize).
Then each selected has a user control (ucSchedulerDay) this is added as a row to the TableLayoutPanel. So take a single day for example, you would have this:
TableLayoutPanel (Dock = Fill; 1 Column (100%); 1 Row (Autosize).
- (Row1 Column1) ucSchedulerDay
So the ucSchedulerDay is just a user control that houses a GroupBox (Dock=Fill) and a FlowLayoutPanel (also dock=fill inside the groupbox)
For each lorry I have another usercontrol added to the FlowLayoutPanel (these have a fixed width) so essentially what I have is the following for one single day
TableLayoutPanel (as above (also forgot to mention that AutoScrollBars=True)
- (Row 1 Column 1) ucSchedulerDay (Dock=Fill(done in code when added))
- GroupBox (Dock=Fill)
- FlowLayoutPanel (Dock=Fill)
- ucLorryDay1
- ucLorryDay2
This works fine as long as all the lorries fit on the screen (see above), so for one day with 2 lorries(or even up to 5 on my monitor) then it's ok. However, if I select two days or make the screen smaller, instead of showing the scroll bars but generally having the same layout, it cuts some of the ucLorryDays up and just doesn't display others.
Note on the above pic how the grey lorry is cut off, even the scroll bar doesn't extend that far.
I don't understand why this isn't working. I would really appreciate any help on this, please let me know if you need more information.
Ok, So I think that the nested GroupBox/UserControl-GroupBox idea was where it all went wrong. I have fixed this by updating the original form to do the following:
pnlLorries.Controls.Clear();
DateTime dt_start = monthView1.SelectionStart;
DateTime dt_end = monthView1.SelectionEnd;
int rowCounter = 0;
for (DateTime dt = dt_start; dt.Date <= dt_end; dt = dt.AddDays(1))
{
Label lbl = new Label();
Font ft = new System.Drawing.Font("Calibri", 12);
lbl.Text = dt.ToShortDateString();
lbl.Font = ft;
pnlLorries.Controls.Add(lbl, 0, rowCounter);
rowCounter++;
FlowLayoutPanel pnl = new FlowLayoutPanel();
pnl.Dock = DockStyle.Fill;
pnl.AutoSize = true;
DataTable tbl = cDALSettings.DB.GetCannedTable("select * from lorry");
// Now we simply add these controls to the panel...
foreach (DataRow row in tbl.Rows)
{
ucLorryDay ld = new ucLorryDay(dt, cTypes.ToInt(row["id"]), this);
pnl.Controls.Add(ld);
}
pnlLorries.Controls.Add(pnl, 0, rowCounter);
rowCounter++;
}
So I create it all and add in the label. The downside is of course that it is not in a neat little box but even when I did it this way with a groupbox it came back with the same results I was experiencing before. I suppose the problem was the panel inside a panel (inception style).

M-dialog - show number of char that are left when the user typing?

Only in the System.Console do I get the result, it print the number of characters that are left, and it update it
So whats wrong, how do i update it in the Element?
Hope you guys can help me with this.
Console:
Characters typed(left): in Value it should write the number of characters that are left.
var root = new RootElement ("Send Message");
var messageElement = new MultilineEntryElement ("", "0123456789")
{
Editable = true,
Height = 120
};
var messageSection = new Section ();
int leangtOfChar = 200 - messageElement.Value.Length;
var lengthElement = new StringElement ("characters typed:", leangtOfChar.ToString());
messageElement.Changed += delegate {
System.Console.WriteLine (leangtOfChar.ToString ());
//lengthElement.Value = (leangtOfChar.ToString());
};
root.Add(messageSection);
The problem is that when you update a normal StringElement it doesn't automatically update the attached cell.
To force an update for just that cell, I would recommend either:
calling root.Reload(lengthElement, UITableViewRowAnimation.Fade) after you change the value
or using a custom element/cell type instead of StringElement - basically you could model this on the simple code in BooleanElement - but use a UILabel as the accessory instead of a UISwitch.
If you wanted to go further - to actually allow StringElement to be updated without a reload - then you can do this by modifying the Element class to track whether a cell instance is currently attached. I've done exactly that in a databinding branch of monotouch.dialog - https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross.Dialog/Dialog/Elements/Element.cs - but this is almost certainly overkill for what you are trying to do right now.

winforms: datagridview: height (autosize) depending on number of rows

in one of my forms a datagridview displays data from a database (of course the number of data (so number of rows) can change). The database connection is in the form load event. I just cant figure out how the height of the whole datagridview is autosized, depending on the number of rows it displays.
This is what I managed to find, and it runs fine so far :
int GetDataGridViewHeight(DataGridView dataGridView)
{
var sum = (dataGridView.ColumnHeadersVisible ? dataGridView.ColumnHeadersHeight : 0) +
dataGridView.Rows.OfType<DataGridViewRow>().Where(r => r.Visible).Sum(r => r.Height);
return sum;
}
Thanks to this, I encapsulated my DataGridView in a UserControl so I could implement AutoSize correctly :
// This is in a user control where the datagrid is inside (Top docked)
protected override void OnResize(EventArgs e)
{
if (AutoSize)
{
var height = this.GetDataGridViewHeight(this.dataBoxGridView);
this.dataBoxGridView.Height = height;
this.Height = height +this.Padding.Top + this.Padding.Bottom;
}
}
I did not try (yet) to build a Custom Control directly from the DataGridView to implement this.
If you set DataGridView.AutoSize == true then as you add more rows the grid gets longer. Otherwise you get scrollbars. Unless you've set ScrollBars == Null || Horizontal, in which case the rows just disappear of of the end.
For some reason, DataGridView.AutoSize can only be set programmatically. And there're some odd behaviours observable when you put the grid inside an autosizable control. It doesn't seem to respond to the size of the grid.
I ended up calculating the expected size of the grid from the column, row, header, margin, padding and border sizes, and then sizing the control containing the grid and anchoring the grid on four sides. Felt really clunky but it's the best I could come up with. If you're still around, comment and I'll see if I can find the code, I don't have it on hand.
MSDN says "This property is not relevant for this class."
MSDN: DataGridView.AutoSize Property
This is how i did. to set height of DataGridView you can use its Set Height property.On form load you can use this code to hide datagridview.
dataGridViewName.Height = 0;
Then While fetching Rows from Database. we can use below method to get datagridview Height according to number of Rows.
private int dataGridViewHeight()
{
int sum = this.dataGridViewName.ColumnHeadersHeight;
foreach (DataGridViewRow row in this.dataGridViewName.Rows)
sum += row.Height + 1; // I dont think the height property includes the cell border size, so + 1
return sum;
}

Categories