I have a vertical WarpPanel that is populated with controls at runtime. The types and number of controls is determined at runtime. It works good with controls that have a fixed height, but controls that expand according to their contents (e.g. Listbox) often create a new column. I somehow need to force the panel to place the controls in the last column as the other, fixed height controls UNLESS the space available in the column is less than MinHeight of the control we are trying to place. Setting Height or MaxHeight for the controls is not an option.
The image below demonstrates the problem. The two listboxes' widths are the same, but instead of putting them in the same column, one of them ends up half-invisible.
Instead of that I would expect to get this:
Is there any way to implement this without making/using a custom panel?
Code:
**Panel:**
<WrapPanel x:Name="wp" Orientation="Vertical">
**Adding controls:**
private void AddControl(bool isListBox)
{
if (isListBox)
{
var lb = new ListBox();
lb.MinHeight = 310;
lb.Width = 310;
lb.MaxWidth = 310;
lb.MinWidth = 310;
wp.Children.Add(lb);
}
else
{
var cb = new ComboBox();
cb.Width = 310;
cb.MaxWidth = 310;
wp.Children.Add(cb);
}
}
The problem here is that the WrapPanel is always going to give the ListBox as much space as it wants, up to the available height in the WrapPanel. What you want to have happen is something more like a UniformGrid effect, but only for expanding Height elements in the column and only as long as the MinHeight constraint isn't violated. This gets a bit tricky, especially if you have other fixed height elements in-between the ListBox elements or other elements with different MinHeight constraints.
It's possible to do the computation, but I think you'll need to create a custom Panel to get this behavior. Basically, it would work like the WrapPanel code, but when you have variable height elements (elements whose Measure returns unbounded size in the wrap dimension), it needs to look at their MinHeight and accumulate these with the fixed Height elements in the same column, ultimately dividing the remaining non-fixed Height by the number of variable elements, to produce the height(s) that will be provided in the Arrange pass.
Related
There are too many items in the menu strip item.
Like this:
It looks very long and bad. I want to add a scroll bar to menu strip items.
I want to see 3 of these items with the help of the scroll bar.
How can I do it?
You can set the maximum height of a MenuItem's DropDown using the ToolStripMenuItem.DropDown MaximumSize property.
You can determine the height of the DropDown based on the maximum number of Items you want to show. Or any other measure that makes sense in your scenario (maybe a measure that fits the current ClientSize.Height of a Form).
To specify a height relative to a maximum number of sub items (here, maxSubMenuItems), sum the Height of the first maxSubMenuItems sub items and use this measure to set the MaximumSize property.
The standard (top and bottom) scroll buttons will appear.
Here, I'm using a maxHeight + (maxHeight / maxSubMenuItems) value, to add some space, at the top and bottom of the dropdown, otherwise a menu may not fit in and it also looks better :)
Insert this code in the Form's Constructor (after InitializeComponent()) or in Form.Load:
int maxSubMenuItems = 6;
if (toolStripMenuItem1.DropDownItems.Count > maxSubMenuItems) {
int maxHeight = toolStripMenuItem1.DropDownItems
.OfType<ToolStripMenuItem>()
.Take(maxSubMenuItems)
.Sum(itm => itm.Height);
toolStripMenuItem1.DropDown.MaximumSize =
new Size(toolStripMenuItem1.DropDown.Width, maxHeight + (maxHeight / maxSubMenuItems));
}
► This can come in handy when you have to show a list of sub-items that represent Fonts, for example. So you may want to show a string drawn using the Font name in the list. Or similar situations, when you have to present a single, potentially long, dropdown.
In other cases, try to limit the number of items per ToolStripMenuItem using sub menus.
I have several panels in a windows form application, they are sorted in two columns and maximally 4 rows, so maximally 8 panels. The number of elements included in every single panel changes during the runtime, so not to waste place on monitor I set all of them to autosize. The problem is, I dont know how I can keep them placed correctly, like how to make that when the first one shrinks that the other three come up a bit so there is not too much space between them.
Try to use the TableLayoutPanel or the FlowLayoutPanel (or possibly even a SplitContainer). They all can be very useful for this kind of task. You find them in the section Containers in the Toolbox. You can keep the right distance by setting the margins of the panels appropriately. The TableLayoutPanel gives you different options for sizing the rows and columns (absolute or percent size or auto). Also by working with the Dock or the Anchor properties of the panels and controls you can attain a dynamic behavior when resizing or adding and removing controls.
You might also have to set the MinimumSize and MaximumSize properties of the controls.
You can add controls like this the TableLayoutPanel
int count = tableLayoutPanel1.Controls.Count;
int newColumn = count % 2;
int newRow = count / 2;
if (newRow >= tableLayoutPanel1.RowCount) {
tableLayoutPanel1.RowCount++;
// Set appropriate row style
tableLayoutPanel1.RowStyles.Add(new RowStyle { SizeType = SizeType.AutoSize });
}
var newControl = new Button { Dock = DockStyle.Fill };
tableLayoutPanel1.Controls.Add(newControl, newColumn, newRow);
I have a DataGrid that I'm binding to DataTable that got filled by a SQL query. I would like the columns of this grid to be auto-generated with a width of up to a certain number of pixels, but still be expandable by the user if they want it to be wider. Currently the ColumnWidth property on the datagrid is set to SizeToHeader (which doesn't seem to work as described anyway, since it's still sizing to cell content).
Is there any way to set a max generation width?
Setting MaxColumnWidth prevents the user from resizing the column larger than that width.
I've also tried hooking into AutoGeneratedColumns, but since the rows aren't loaded yet the ActualWidth properties just represent the MinWidth I've set. Is there an event that fires once the datagrid finishes loading its initial set of rows?
Iterate over the column collection and get the width for each column. If the width is greater than the threshold set it to your maximum width. You would want to do this after the control has initialized.
This article may be helpful: http://support.microsoft.com/kb/812422
Something like this may work:
foreach(var c in grid.Columns)
{
var width = c.Width;
if(width > threshold)
{
c.Width = threshold;
}
}
From the documentation the Width property set on an individual DataGridColumn takes precedence over the ColumnWidth property. You might be able to iterate over the DataGridColumns in the AutoGeneratedColumns event and set individual Widths and have them stick.
I needed to modify rtalbot's code slightly to get this to work for my WPF/.net 4.5 project ... actualwidth and datagridlength:
// fix extra wide columns
foreach (var c in dgMain.Columns)
{
var width = c.ActualWidth;
if (width > 200)
{
c.Width = new DataGridLength(200);
}
}
I create TextBox on the Canvas like this:
TextBlock t = new TextBlock();
t.Foreground = new SolidColorBrush(...);
t.FontSize = 10;
t.HorizontalAlignment = HorizontalAlignment.Left;
t.VerticalAlignment = VerticalAlignment.Top;
t.Text = ...;
Canvas.SetLeft(t, "f(width)");
Canvas.SetTop(t, ...);
canvas.Children.Add(t);
I want to know width of this TextBlock, because Left coordinate depends on this.
Can I do it? ActualWidth is 0.
Thanks.
Before you add it, call Measure on it, then use the DesiredSize.
Edit: This is OK to do because Canvas does not affect the size of the element once placed. If you added it to, say, a Grid with a fixed-size row, this wouldn't give you the real Height once added since the adding to the Grid would change it.
As Mario Vernari points out, if you have real complex positioning needs it's pretty easy to override ArrangeOverride (and sometimes MeasureOverride) and make a custom panel. Canvas is actually written this way, as is StackPanel, etc. They are all just specific-measuring and arranging panels, and you can make your own, too.
I have a DataGrid with some template columns that contain another DataGrid. My problem is that if some rows have a lot of content in them their height doesn't adjust so the whole content is visible, but rather it's cut off, giving the impression that the rows overlap. However, as soon as I add a new row to the grid or add a new row to the mini-grid inside one of the main grid's rows, the layout gets updated and the row heights are resized correctly.
So the problem is only when loading the grid the first time.
Is there a way to force the grid to size the rows heights to their content?
Thanks
I had some serious trouble with this (bug?) today, so I'll share what I tried and what almost worked... (And hope that someone knows an actual fix)
In my case the bug only appeared when there were 10 or more rows. Out of the rows, ten first rows would in some cases have too small height for the contents. (I first thought that the nine items were drawn on top of each other, which was stupid of me.) There are quite many columns, so there's a scrollbar. Clicking on the scrollbar resizes the heights to proper values.
Some things that in my experience do not work:
Changing virtualization settings had no effect.
InvalidateVisual() and InvalidateArrange() don't work. Tried both datagrid and its parent.
Changing the height of datagrid did not work (although I'm not quite happy with my tests here)
Forcing the datatemplates of the cells to a specific size did not have an effect.
Placing the datagrid inside a scrollviewer (so that the datagrid would have all the space it could ever need...) did not work.
However:
The one thing (I found) that the datagrid seems to respect is MinRowHeight-setting, so now I've got there a moronic value and I'm hoping that this won't cause problems later on when the datatemplates are modified.
I experienced the same bug with the DataGrid that comes with the .NET Framework 4.0.
Under certain circumstances (no horizontal scrollbar, window is bigger than a specific size, ...) the rows were not displayed correctly but placed on top of another (with a small offset), so just the last row was completely visible.
First I tried to perform a UI-action automatically after the rows are filled in the DataGrid, so the layout is updated. But then I found out, that you can just re-render the control using the dispatcher, which, in my case, fixed the bug eventually.
My whole code-change basically is this (right after filling the DataGrid):
Action emptyAction = delegate() { };
myDataGrid.Dispatcher.Invoke(DispatcherPriority.Render, emptyAction);
I'm not sure what is this, but you could try call InvalidateVisual(), some time later, when element is loaded. This forces complete new layout pass...
The DataGrid in my UserControl is doing the same thing. In this example, there are only 32 rows of data with five DataGridTemplateColumns consisting of an <Image> and four <TextBlock>s.
My control shows search results, if I rerun the same search it does not always do this. In addition, the cropping only occurs, roughly, on the first pageful of results. Rows further down are ok.
Using InvalidateVisual() does not help.
If anyone has any ideas on how to solve this or can indicate if this is a known issue with that control, I'd be interested in hearing about it.
In my case I just needed to add the first row before the loop adding extras.
I wanted 4 columns and n rows like this:
private void InitToList() {
Grid wp = new Grid();
wp.Margin = new Thickness(0);
wp.ColumnDefinitions.Add(new ColumnDefinition());
wp.ColumnDefinitions.Add(new ColumnDefinition());
wp.ColumnDefinitions.Add(new ColumnDefinition());
wp.ColumnDefinitions.Add(new ColumnDefinition());
wp.RowDefinitions.Add(new RowDefinition()); // adding this fixed the overlapping
int curCol = 0;
int curRow = 0;
foreach (string name in toIds) {
if (curCol >= wp.ColumnDefinitions.Count()) {
wp.RowDefinitions.Add(new RowDefinition());
curCol = 0;
curRow++;
}
CheckBox cb = new CheckBox();
cb.Name = String.Format("{0}Check", name.ToLower().Replace(" ", ""));
cb.IsChecked = false;
cb.Margin = new Thickness(5, 5, 5, 5);
cb.Content = name;
Grid.SetColumn(cb, curCol);
Grid.SetRow(cb, curRow);
wp.Children.Add(cb);
curCol++;
}