Is this a bug?

Jul 3, 2013 at 5:00 PM
Markus,
In EditForm (wpf.layout.editform), there is a method MergeColumnIntoOtherColumn(...) that generates an error if I use stack panels with too many items. The frustrating thing is that I am not sure what constitutes too many items...

Anyway, the error is: "Index is out of range" on the foreach loop on line 435.

The debugger says that columns has a count of 1 and sourceColumn is set to 1 - which would be out of range since that is the second element.

When I look at the calling code (on line 409), it is passing 1 as a hard coded value for sourceColumn.

This is with a xaml file with the following header:
<mvvm:View xmlns:Layout="clr-namespace:CODE.Framework.Wpf.Layout;assembly=CODE.Framework.Wpf"  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:mvvm="clr-namespace:CODE.Framework.Wpf.Mvvm;assembly=CODE.Framework.Wpf.Mvvm"
        xmlns:Controls="clr-namespace:CODE.Framework.Wpf.Controls;assembly=CODE.Framework.Wpf"
        IconResourceKey="CODE.Framework-Icon-List"
        Title="Customer Edit"
       Style="{DynamicResource CODE.Framework-Layout-EditFormLayout}"
           >

 <StackPanel Orientation="Vertical" >
   ....
Happy 4th!,

Fletcher
Coordinator
Jul 8, 2013 at 12:21 AM
Hmmm... that is a good question. I think it is probably just a matter of the stack panel growing too large or something. Basically, when you use an EditFormLayout, it always uses one control for the "label" and the next for the "edit" part and so on. In your case, the stack panel would act as the label. But I guess it would be a very very large and unusual label :-). It should still work I guess, but it is certainly possible that there is a fringe-case bug.

So two questions:
  1. What exactly are you trying to accomplish? Maybe there is a better way to do what you want to do?
  2. Can you send me your complete view definition, so I can see if I can repro the problem?
Thanks
Markus
Jul 8, 2013 at 8:18 PM
Markus,

I have tried adding mvvm:View.IsStandAloneEditControl="true" to the Stack declaration like below, but that didn't seem to fix it:
 <StackPanel Orientation="Horizontal" mvvm:View.IsStandAloneEditControl="true">
Rather than waste your time with the complete view definition, let me explain what I am trying to do.

I want to have a form that can resize and redraw the controls based on the form size. But some controls need to be together (like address fields, area code/phone, first/middle/last name, etc.)

So I want to group those in a horizontal orientation (hence the stackpanel).

Of course, then I want to do much more because then I need to group those controls again - either in a pageframe, expanders, or a treeview. So customer could sections on financial info, Customer type/territory/sales rep/etc., one with a list of employees, another for their fleet vehicles, various locations etc. But for now, I am just trying to group similar controls so they stay together (where possible) if the form resizes.

So say I have an address where I want to get the following:
Street Number
Street Prefix (E,N,W,S,etc.)
Street Name
Street Suffix (Ave, St., Blvd, etc)
Room #

Then on the next line, but still part of the address:
City
County
State
Postal Code
Territory

Then the above are under the company name.

Below them (or on a different tab) might be the section on Financial details, then the other data mentioned above.

I have a fair amount of leeway in the controls I use to represent the data, but not in the order, grouping, or breakdown of the data.

Thanks,

Fletcher
Developer
Jul 11, 2013 at 1:29 PM
If I follow the description of your desired layout then you should take a look at the mvvm:View.GroupBreak and mvvm:View.ColumnBreak attributes on the edit form. the complete Quick Tip is here: https://codeframework.codeplex.com/wikipage?title=Automatic%20Layout%20and%20Elasticity&referringTitle=Quick%20Tips

The thing to remember is that the EditFormLayout can be utilized anywhere, not just at the view level. So if you have a lot of controls you can set the view as a CODE.Framework-Layout-StandardFormLayout, then place a standard TabControl on the form. Inside each TabItem place a <mvvm:View Style="{DynamicResource CODE.Framework-Layout-EditFormLayout}"> to do the edit control layouts. Something like this:
            <TabControl Grid.Row="1" 
                    TabStripPlacement="Left"
                    Margin="5" Padding="3">
            <TabItem Header="Billing/Shipment">
                <mvvm:View Style="{DynamicResource CODE.Framework-Layout-EditFormLayout}">
                    <!-- Person Column -->
                    <Label Content="First Name" mvvm:View.GroupTitle="Billing Info"/>
                    <TextBox Text="{Binding BillTo.FirstName}"/>

                    <Label Content="Last Name" />
                    <TextBox Text="{Binding BillTo.LastName}"/>

                    <!-- other controls in this column... -->

                    <!-- Card Column -->
                    <Label Content="Number"
                           mvvm:View.ColumnBreak="True"
                           mvvm:View.GroupTitle="Card Info"/>
                    <TextBox Text="{Binding CardInfo.CardNumber}"/>

                    <Label Content="Expiration Month"/>
                    <TextBox Text="{Binding CardInfo.ExpirationMonth}"/>

                    <Label Content="Expiration Year"/>
                    <TextBox Text="{Binding CardInfo.ExpirationYear}"/>

                    <Label Content="Amount"
                           mvvm:View.GroupBreak="True"
                           mvvm:View.GroupTitle="Order Summary"/>
                    <TextBox Text="{Binding OrderSummary.Amount}"/>

                    <Label Content="Description" />
                    <TextBox Text="{Binding OrderSummary.Description}"/>
                </mvvm:View>

            </TabItem>
            <TabItem Header="Products">
                <!-- another edit form could go here -->
            </TabItem>
        </TabControl>
will render this (theme dependent):
Image
Coordinator
Jul 12, 2013 at 12:40 AM
In addition, you can let controls flow together. For instance, if you have an address area that includes City, State, and Zip, which you want on one line, you can do this:
<Label>City, State, ZIP:</Label>
<TextBox Text="{Binding City}" />
<TextBox Text="{Binding State}" mvvm:View.FlowsWithPrevious="True" />
<TextBox Text="{Binding Zip}" mvvm:View.FlowsWithPrevious="True" />
Markus
Jul 12, 2013 at 1:06 AM
Jeff,

I am trying to do something a little more complex. I have to keep the format similar enough that the users won't complain and will be able to use it without having to learn something new.

So I need to keep some fields together, but laid out horizontally. I have been trying to use the group and column breaks, as well as mvvm:View.IsStandAloneEditControl="true" (for checkboxes, etc.). But I seem to need something like a stack or wrap panel so that the fields lay out horizontally, not vertically. Also, I want to be able to group them in a way that I can resize the form and more (or less) data will appear

It may be that I simply need to use the Ex version of either panel, but I can't find one.

Here is a sample of the type of form I am trying to create:
Image

If you look at the address, it's really important that the street information stays grouped together. But the 3 boxes could be vertical or horizontal. And the tabs could be tabs located on any side, expander boxes, or even a treeview control. You get the idea.

Thanks,

Fletcher
Coordinator
Jul 12, 2013 at 1:18 AM
Edited Jul 12, 2013 at 1:19 AM
Hmmm... not sure that the EditForm is the most ideal approach for this, because the edit form tries to create more overall columns.

With that said: You could probably use multiple EditForms (or views with edit form styles). Basically make a vertical stack of EditForms. Then, it would be something like this:
<StackPanel>
    <EditForm>
        <Label>Label</Label>
        <TextBox />
        <Label>Label</Label>
        <TextBox />
        <Label>Label</Label>
        <TextBox />
        <Label mvvm:View.ColumnBreak="True">Type:</Label>
        <ComboBox />
        <CheckBox mvvm:View:IsStandAloneEditControl="True">Label</CheckBox>
        <Label mvvm:View.ColumnBreak="True">Label</Label>
        <TextBox />
        <Label>Label</Label>
        <TextBox />
    </EditForm>
    <EditForm>
        <Label>Date:</Label>
        <TextBox />
        <Label mvvm:View.ColumnBreak="True">Time:</Label>
        <TextBox />
        <Label mvvm:View.ColumnBreak="True">Label:</Label>
        <TextBox />
        <Label mvvm:View.ColumnBreak="True">Label:</Label>
        <TextBox />
    </EditForm>
    <EditForm>
        <Label>Address:</Label>
        <TextBox />
        <ComboBox mvvm:View:FlowsWithPrevious="True" />
        <TextBox mvvm:View:FlowsWithPrevious="True" />
        <ComboBox mvvm:View:FlowsWithPrevious="True" />
        <ComboBox mvvm:View:FlowsWithPrevious="True" />
        <TextBox mvvm:View:FlowsWithPrevious="True" />
        <Label>Location:</Label>
        <TextBox />
        <Button mvvm:View:FlowsWithPrevious="True" />
         .... and so on...
    </EditForm>
</StackPanel>
So I think this could get you pretty far. But then again, maybe a different automated approach may be better. The FlowForm could be interesting for instance. Or of course, if the requirements become very specific, you might just be better off doing an good old fashioned manual form layout. That's still fine too :-)


Markus


PS: I am just typing in the above code snippet here manually. I think I got all the property names right. Anyway: You get the idea :-)
Jul 12, 2013 at 11:31 PM
Markus,

Getting back to the original purpose of the thread...

Try implementing a geek themed page with the code you have above. Then try to resize it. Here is the xaml I generated:
<mvvm:View xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:mvvm="clr-namespace:CODE.Framework.Wpf.Mvvm;assembly=CODE.Framework.Wpf.Mvvm"
           xmlns:Controls="clr-namespace:CODE.Framework.Wpf.Controls;assembly=CODE.Framework.Wpf"
           xmlns:Layout="clr-namespace:CODE.Framework.Wpf.Layout;assembly=CODE.Framework.Wpf"
           Title="New View"
           >
    <StackPanel>
        <mvvm:View Style="{DynamicResource CODE.Framework-Layout-EditFormLayout}">
            <Label>Label</Label>
            <TextBox />
            <Label>Label</Label>
            <TextBox />
            <Label>Label</Label>
            <TextBox />
            <Label mvvm:View.ColumnBreak="True">Type:</Label>
            <ComboBox />
            <CheckBox mvvm:View.IsStandAloneEditControl="True">Label</CheckBox>
            <Label mvvm:View.ColumnBreak="True">Label</Label>
            <TextBox />
            <Label>Label</Label>
            <TextBox />
        </mvvm:View>
        <mvvm:View Style="{DynamicResource CODE.Framework-Layout-EditFormLayout}">
            <Label>Date:</Label>
            <TextBox />
            <Label mvvm:View.ColumnBreak="True">Time:</Label>
            <TextBox />
            <Label mvvm:View.ColumnBreak="True">Label:</Label>
            <TextBox />
            <Label mvvm:View.ColumnBreak="True">Label:</Label>
            <TextBox />
        </mvvm:View>
        <mvvm:View Style="{DynamicResource CODE.Framework-Layout-EditFormLayout}">
            <Label>Address:</Label>
            <TextBox />
            <ComboBox mvvm:View.FlowsWithPrevious="True" />
            <TextBox mvvm:View.FlowsWithPrevious="True" />
            <ComboBox mvvm:View.FlowsWithPrevious="True" />
            <ComboBox mvvm:View.FlowsWithPrevious="True" />
            <TextBox mvvm:View.FlowsWithPrevious="True" />
            <Label>Location:</Label>
            <TextBox />
            <Button mvvm:View.FlowsWithPrevious="True" />
        </mvvm:View>
    </StackPanel>
</mvvm:View>
You may have to try to resize it a couple of times (it crashed on my second attempt.

The rest of the info above is VERY MUCH appreciated. The mvvm:View:FlowsWithPrevious="True" is really cool (except that I have to put it on the label and the text box, so in metro view, it looks hokey because the first label is above the textbox and then all the other labels and textboxes on in the same line. But since I don't use Metro, I can probably live with that. But it might be nice to have something like "controlPairFlowsWithPrevious" where I only add that to the label and it is used by the code that positions the label (and then textbox.) And then, of course, having a "controlPairCanBeMoved" or similar that says that, should we be short on space, this controlpair can be moved to the next row even though I have a controlPairFlowsWithPrevious specified. But you probably already have something like this and I just haven't found it yet (boy, I wish Intellisense worked in Xaml files....)

Thanks,

Fletcher
Coordinator
Jul 26, 2013 at 2:02 PM
FYI: Not ignoring this, but I will have to dig through this in detail...

Markus
Aug 8, 2013 at 6:36 PM
Markus,

Any more thoughts on the issue?

Thanks,

Fletcher
Coordinator
Aug 11, 2013 at 11:20 PM
Not so far, unfortunately. It will take me some time to debug through that in detail. Unfortunately, I have been exceptionally busy with several different client projects, so I have not been able to get to this at this point.

On a side-remark: If you end up having to put things like FlowsWithPrevious="True" on just about every single thing, I wonder whether that particular layout element is the right approach. Maybe a different automated approach works better, or maybe that particular form just isn't a good candidate for auto-layout.


Markus
Aug 12, 2013 at 4:08 PM
Markus,

The idea is to get the layout to look sort of like the layout in the message (above) from Jul 11 at 6:06 PM.

Right now, the layout tries to put every field in a vertical column, then start a new column (assuming I have a new column tag referenced). In my case, I need to have similar items on the same row.

I may try to look at your code and try to get the fields to come out in a horizontal layout and be able to force a row wrap (vs. a column wrap) - which would solve the problem and eliminate the need for "FlowsWithPrevious".

The problem with the sample code above only happens when you resize - but then does so consistently. It's not an impediment for me right now, but may come up more when more people start using similar approaches.

Take care,

Fletcher
Coordinator
Aug 13, 2013 at 11:30 AM
There actually is a separate layout control that does that.

Markus
Aug 13, 2013 at 11:23 PM
Markus,

Ok, that answer is a tease if ever there was one. Could you tell me which one? Or do I have to go through them one at a time until I find it?

Thanks,

Fletcher
Aug 16, 2013 at 7:35 PM
Edited Aug 16, 2013 at 7:42 PM
Markus,
Ok, I have tracked down part of the problem. Referencing the code in the build I have (which should be the current build here on Codeplex):

In the EditForm.cs file, in the protected override Size MeasureOverride(Size availableSize) method, you check the requiredSize. Here is a snippet of the affected code:
else if (LayoutElasticity == LayoutElasticity.LayoutAndReflow && requiredSize.Width - 1 > availableSize.Width)
{
    // We will re-flow the columns
    var columnsFit = false;
    while (!columnsFit)
    {
        MergeColumnIntoOtherColumn(_columnsUsed, 1, 0);
        requiredSize = PerformMeasurePass(_columnsUsed);
        columnsFit = requiredSize.Width <= availableSize.Width;
        if (!columnsFit && _columnsUsed.Count == 1) columnsFit = true; // This is the best we can do
    }
}
The 1st command in the while loop is the culprit. Here you call a method that will merge columns together. The problem is that if there is only one column, this will crash.

The following snippet solves the problem for me:
while (!columnsFit && _columnsUsed.Count > 1)
And with this, you can probably delete the IF statement that checks the columnsUsed at the end of the loop.

It appears that if this is done, it works fine with the scroll bars appearing as expected.

One other thought. In the code, you have the following comment in the method documentation:
Classes can override this method to define their own Measure pass behavior.
It might be nice to include a link that explains the basics of how to do this. You know the code and it's obvious to you. Some of us (ok, at least me) could use a hint or two. And yes, I am going back through your article on styling at http://www.code-magazine.com/article.aspx?quickid=1206101&page=1 after I finish writing this.... :)

Thanks,

Fletcher
Coordinator
Aug 17, 2013 at 9:20 AM
Thanks. I will take a look at that.

As far as the tease goes :-): Sorry about that. Here's more detail:

For edit form layouts, there is a layout panel called EditForm. Similarly, there is one that creates more of a flow approach, which controls flowing in from left to right and then over into the next column (but with some extra logic added to align first labels in a row and such and make sure certain controls flow over together). This layout panel is called FlowForm.

Both layout panels are used in a style so you can just apply that style to a View and automatically have these layout approaches applied (but you can of course also use these controls individually in other scenarios, such as manually using them as the layout panel for any items control). The styles are called CODE.Framework-Layout-EditFormLayout and CODE.Framework-Layout-FlowFormLayout respectively.


Markus