WPF Grid won’t resize to fit nested content - c#

I’m programmatically inserting grids into grids, for the first nested, it will work perfect. But then from the second it stops resizing to fit for the content.
The only defaults I override are the MinWidth and the MinHeight.
EDIT:
Each time I'm creating a grid, I add a stackpanel (with lable inside) to each cell. Then I Insert a nested grid to that stack panel.
As seen above, the grid that's being marked with the green thing, dose not affect the mainGrid size.
Thanks

Apparently the grid restricts it's maximum size, I don't know for what purpose.
Hopefully that won't back fire at me later. but for now, all I had to do was create and use a custom grid with unlimited 'resize room'.
public class CustomGrid : Grid
{
protected override Size MeasureOverride(Size availableSize)
{
availableSize = new Size(availableSize.Width + double.MaxValue, availableSize.Height + double.MaxValue);
return base.MeasureOverride(availableSize);
}
}
There are probably more legitimate solutions to this problem, but for the moment that's all I need.

Related

WPF MeasureOverride return value

I am writing MeasureOverride implementation and there is one point I am kind of stuck.
The return value of the function.
This is the code.
protected override Size MeasureOverride(Size availableSize)
{
Double cHeight = 0.0;
Double cWidth = 0.0;
Size size = new Size(0, 0);
foreach (UIElement child in InternalChildren)
{
child.Measure(new Size(availableSize.Width, availableSize.Height));
if (child.DesiredSize.Width > cWidth)
{
cWidth = child.DesiredSize.Width;
}
cHeight += child.DesiredSize.Height;
}
size.Width = double.IsPositiveInfinity(availableSize.Width) ? size.Width : cWidth;
size.Height = double.IsPositiveInfinity(availableSize.Height) ? size.Height : cHeight;
return size;
}
My understanding is that returning an empty Size object is an indication that the element will use all the space available. However in this case when the space available is infinite positive, then it is returning zero.
Should it not be other way around. When infinite space is available then use only the space needed by the child elements? Otherwise constrain itself to use whatever space is available?
size.Width = double.IsPositiveInfinity(availableSize.Width) ? cWidth : availableSize.Width;
size.Height = double.IsPositiveInfinity(availableSize.Height) ? cHeight : availableSize.Height;
This one is kind of difficult to answer. It depends on what you want to achieve.
Probably you know that the measure phase determins the desired sizes only. The arrange phase fiddles with final values then.
I can imagine a panel with a behavior as in the code above. It could avoid a parent ScrollViewer to reserve too much space in case a child of our panel desires very much space (for example because it is an ItemsControl with many items itself). By returning zero the surrounding ScrollViewer would not reserve this space and in the arrange phase our panel could occupy space as needed although we returned zero.
It would be a very special case, but I was dicussing this very problem with a collegue today when he had a DataGrid along other elements within a ScrollViewer.
Without the code in "Arrange" this is all speculative but it's a possible usage of such code.
With MeasureOverride, you are returning the size that your control wants to be. If MeasureOverride is given infinite bounds then you can make it the size that holds all of the children or not; it is up to you. On the other hand, if you return a size that is larger than you are given, your control parent might accept that and just cut off your control later. For instance, if your control is in a grid and the grid width is set to 500, your MeasureOverride function will get passed an available width of 500. You can return 600 for the width and you will get that 600 width in the ArangeOverride function, but the parent grid will still only give your control 500 width and your content will just get cut off.
I tried this with my own PriorityStackPanel control written for WinRT (Windows 8.1 Store App) and it worked as I described. I think that WPF is just about identical in this case.
In WinRT, there is no "empty" size object. The size object will have some value set for it. NAN and PositiveInfinity both cause exceptions so you really should be returning the size that you want for your control. How you pick that size is up to you but it can't be a "you can decide for me: value like an empty size object (in WinRT). Even if you can return that in WPF, I advise against it since it makes the code less-than-clear. Just return the available size that you were given if that is what you need. Return a larger size if you need more room, but don't expect the container to show all of your content if you ask for more room than it is giving you.

WrapPanel creates a new column for each dynamically expanding control

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.

How to determine the proper maximum value for scrollbar c# .NET (how does windows do it?)

Before I go into my question, let me explain my setup:
First: I have a PictureBox that holds a Bitmap which is generated at runtime. This Bitmap can be different widths but always the same height.
Second: PictureBoxes do not support scrolling, therefore, I have the PictureBox docked in a panel. Initially, I had used the panel's autoscroll feature, but abandoned that after I discovered through this article that PictureBoxes have a size limit. I also learned that it's better to instead have small PictureBoxes and only draw what needs to be seen instead of the whole image.
Third: So I added a HScrollBar, which is fine and dandy, but I can't seem to figure out the math behind how big to make the scroller. I tried setting the maximum of the scrollbar to the length of the bitmap, but as you can see the size of the scroller is much smaller in mine than the one Windows puts in if I use the autoscroll feature.
My question is, what is the math behind the scroller size and how do I emulate that in my custom scrollbar?
Let me know if my question is unclear and I will try my best to make it more understandable. And thanks in advance for your help!
I figured out what was the problem. Perhaps I should have tried a little longer. :)
The answer lies in the LargeChange property. I let the Maximum at the total width of the bitmap and then set the LargeChange to the width of what I wanted to show. (i.e. the width of the PictureBox)
The size of the "scroller" is determined by the ratio of the value of LargeChange to the value of Maximum. For example, if the width to show (LargeChange) is 100 and the total width (Maximum) is 300 then the "scroller" size will be 1/3 of the scrollbar length. (100/300).
I got same problem too and tried to figure it out, I have a panel which contain another panel inside it called panelChild, and the default scrollbar is small, I need lager scrollbar, so I use HScrollBar to do that (display over-top of default scrollbar), I post my solution here, may be it helpful to someone
public Form() {
InitializeComponent();
hScrollBar.Maximum = panelChild.Width;
hScrollBar.LargeChange = panel.Width; // panel which contain panelChild, and this hScrollBar will same as panel scrollbar
hScrollBar.Scroll += HScrollBar_Scroll;
}
private void HScrollBar_Scroll(object sender, ScrollEventArgs e)
{
int diference = e.OldValue - e.NewValue;
foreach (Control c in panel.Controls)
{
c.Location = new Point(c.Location.X + diference, c.Location.Y);
}
}

Drawing a chart in WPF C# design questions

I had a project a month ago where I drew a stock chart in an application using Windows Forms. I did this by creating a bitmap that would stretch to the dimensions of the window. This would allow my chart to resize with the window.
I am now expanding the project using WPF. I have been trying to work on my design for the project, but I cant seem to get any idea on the best way to do the same chart. I have looked at canvases, grids, and a few other controls. I thought I was on the right track with the canvas, but when I would resize the window, my drawing would stay in the same spot. I guess the point of my post tonight is to get some ideas to help me brainstorm a design for my project.
All advice and questions are appreciated.
Thanks,
Joseph
(Realizing this addresses at best a subset of this fairly old question, since it's only one chart type...)
Just fwiw, creating a bar graph in a Grid as Ed suggests is pretty straightforward. Here's a quick and dirty version:
Add a Grid to your Window's XAML. Just for testing, here's one that fills the Window entirely.
<Grid>
<Grid
Name="myGrid"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="auto"
Height="auto"
Margin="10,10,10,10"
/>
</Grid>
Now insert these two utility functions somewhere in your project. These provide simple, single-color columns and unstyled, but centered, label text for the x-axis.
I think the only nasty kludge is the maxHeight in the _placeSingleColorColumn call.
Worth mentioning: I don't have labels for the y-axis in this quick & dirty version.
private void _placeSingleColorColumn(Grid grid, Color color, int height, int colNum, int maxHeight)
{
Brush brush = new SolidColorBrush(color);
Rectangle rect = new Rectangle();
rect.Fill = brush;
Grid.SetColumn(rect, colNum);
Grid.SetRow(rect, maxHeight - height);
Grid.SetRowSpan(rect, height);
grid.Children.Add(rect);
}
private void _createLabels(Grid grid, string[] labels)
{
RowDefinition rowDefnLabels = new RowDefinition();
grid.RowDefinitions.Add(rowDefnLabels);
for (int i = 0; i < labels.Length; i++)
{
TextBlock block = new TextBlock();
block.Text = labels[i];
block.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
Grid.SetColumn(block, i);
Grid.SetRow(block, grid.RowDefinitions.Count);
grid.Children.Add(block);
}
}
That's really it. Here's some insanely quickly and dirty example code to create a 10 by 10 grid with some sample data.
public void createGrid10x10()
{
Random random = new Random();
for (int i=0; i<10; i++)
{
ColumnDefinition colDef = new ColumnDefinition();
myGrid.ColumnDefinitions.Add(colDef);
RowDefinition rowDef = new RowDefinition();
myGrid.RowDefinitions.Add(rowDef);
Color color = i % 2 == 0 ? Colors.Red : Colors.Blue;
_placeSingleColorColumn(this.myGrid, color, random.Next(1,11), i, 10);
}
string[] aLabels = "Dogs,Cats,Birds,Snakes,Rabbits,Hamsters,Horses,Rats,Bats,Unicorns".Split(',');
_createLabels(this.myGrid, aLabels);
}
Add one line to your MainWindow constructor, and you're done, afaict.
public MainWindow()
{
InitializeComponent();
this.createGrid10x10();
}
Now you've got a bar graph that'll resize and stay proportional as the window is resized, etc.
Adding more labels (bar values on top, y-axis labels, etc) should be pretty straightforward if you understand the above. Just throw in another column and/or row, create your TextBlocks, and place them in the right locations.
Try using a DockPanel and set LastChildFill to true. Then make your control the last child of the DockPanel.
Funny: I am just making the same thing!
I already developed a chart control, plenty of features. Now I need to renew and extend it with some other function. However, my deal is manage even 100k of points on a single chart, but keeping a good performance on a normal pc. By resizing the window will scale the chart, but not the text eventually placed on it. Also consider that I need a real-time rendering of the data incoming at least 0.5 sec.
All that has been resolved using the old-style bitmap creation, then placing it as a normal image on any wpf control. There are several limitation because there's no "living" objects as in the wpf you have, but the rendering performance is really huge compared to the wpf primitives.
So, unless you have charts with max 100 points to manage, I strongly recommend the hybrid approach. Anyway, it's a very hard task!
Cheers
I am assuming you want to draw your own chart rather than using WPF charts.
Canvas is not usually a good thing to use in WPF because it fixes objects at a specific location and size, exactly as you've seen (though I suppose you could use a Canvas with a ScaleTransform). Grid will take the size of its container, so putting a Grid into a window will make the Grid resize with the window (unless you specify a fixed Width and Height for the Grid). StackPanel will stack things and will attempt to take the minimum size of its content, so that's probably not what you want to use here.
Creating a chart layout inside a panel like a Grid isn't completely simple. If you are doing a bar chart, you could create a Column in the Grid for each bar, assign a percentage width such as star; and the columns would get larger as your Grid expanded with the window. You can use a similar trick by making each Bar a Grid, setting two columns in the Grid, and setting a third level of Grid inside the lowest column, then using percentages for the column heights (e.g., 90star and 10star for 90%, 10% heights). The bars would then grow taller as the window grows taller. You could reserve a Grid row below the bars for labels, and center them under the bars.
Line charts are trickier. You would probably want to create a GeometryDrawing of line segments, and then use a ScaleTransform bound to the window size to make it shrink and grow.
There are a lot of possibilities with WPF, but you'll need to do a bit of leaning and studying first. A book such as Adam Nathan's "Windows Presentation Foundation Unleashed" would quickly give you a lot of knowledge of WPF layout and how to proceed.
Edit: You could also use an empty panel and use its DrawingContext to draw lines, rectangles, text, ellipses, etc. at points you calculated from the current window size.

FlowLayoutPanel autowrapping doesn't work with autosize

.NET Framework / C# / Windows Forms
I'd like the FlowLayoutPanel to automatically adjust its width or height depending on number of controls inside of it. It also should change the number of columns/rows if there is not enough space (wrap its content). The problem is that if I set autosize then the flowlayoutpanel doesn't wrap controls I insert. Which solution is the best?
Thanks!
Set the FlowLayoutPanel's MaximumSize to the width you want it to wrap at. Set WrapContents = true.
Have you tried using the TableLayoutPanel? It's very useful for placing controls within cells.
There is no such thing like impossible in software development. Impossible just takes longer.
I've investigated the problem. If there is really need for Flow Layout, it can be done with a bit of work. Since FlowLayoutPanel lays out the controls without particularly thinking about the number of rows/columns, but rather on cumulative width/height, you may need to keep track of how many controls you've already added. First of all, set the autosize to false, then hook your own size management logic to the ControlAdded/ControlRemoved events. The idea is to set the width and height of the panel in such a way, that you'll get your desired number of 'columns' there
Dirty proof of concept:
private void flowLayoutPanel1_ControlAdded(object sender, ControlEventArgs e)
{
int count = this.flowLayoutPanel1.Controls.Count;
if (count % 4 == 0)
{
this.flowLayoutPanel1.Height = this.flowLayoutPanel1.Height + 70;
}
}
if the panel has initially width for 4 controls, it will generate row for new ones. ControlRemoved handler should check the same and decrease the panel height, or get all contained controls and place them again. You should think about it, it may not be the kind of thing you want. It depends on the usage scenarios. Will all the controls be of the same size? If not, you'd need more complicated logic.
But really, think about table layout - you can wrap it in a helper class or derive new control from it, where you'd resolve all the control placing logic. FlowLayout makes it easy to add and remove controls, but then the size management code goes in. TableLayout gives you a good mechanism for rows and columns, managing width and height is easier, but you'd need more code to change the placement of all controls if you want to remove one from the form dynamically.
If possible, I suggest you re-size the FlowLayoutPanel so that it makes use of all the width that is available and then anchor it at Top, Left and Right. This should make it grow in height as needed while still wrapping the controls.
I know this is an old thread but if anyone else wonders on here then here's the solution I produced - set autosize to true on the panel and call this extension method from the flow panel's Resize event:
public static void ReOrganise(this FlowLayoutPanel panel)
{
var width = 0;
Control prevChildCtrl = null;
panel.SuspendLayout();
//Clear flow breaks
foreach (Control childCtrl in panel.Controls)
{
panel.SetFlowBreak(childCtrl, false);
}
foreach (Control childCtrl in panel.Controls)
{
width = width + childCtrl.Width;
if(width > panel.Width && prevChildCtrl != null)
{
panel.SetFlowBreak(prevChildCtrl, true);
width = childCtrl.Width;
}
prevChildCtrl = childCtrl;
}
panel.ResumeLayout();
}
Are you adding the controls dynamically basing on the user's actions? I'm afraid you'd need to change the FlowLayout properties on the fly in code, when adding new controls to it, then refreshing the form would do the trick.

Categories