WPF-Forge/Forge.Forms

Dynamic form support for columns and dividers

hashitha opened this issue ยท 34 comments

Is it possible to add the divider in the dynamic form loading from a string?

image

Also, is it possible to have columns like this image when loading from a string

image

Hey @hashitha , in loading from a string do you mean from XML? A horizontal divider is <hr /> I think.
For the vertical divider it may work but only on situations like the picture above when the control is placed inline, that's because controls can decide their height but not width when displayed regularly (the width is decided from the column their placed in.

You are right <hr /> works. Where can I find a list of supported tags, I have been looking in the code but couldn't find it yet.

You can find it here https://github.com/WPF-Forge/Forge.Forms/blob/master/Forge.Forms/src/Forge.Forms/FormBuilding/FormBuilder.cs#L289

It's quite limited now, for complex layouts we need to support some tags like <row> or similar. If you have dealt with XML you probably know better than me what is necessary, so feel free to pr.

I found the following in FormBuilder.cs

title
heading
text
br
hr
action

I will add an <img> element and [Image] attribute in a few min and we can write a demo for it.

Thanks, I will give it a try when you are done. Adding the <row> would work

so something like the following will make it all in one line

<row>
<text>Liver:</text> 
<input type="bool" name="NAD" label="NAD" defaultValue="false"></input>
</row>

There are two kinds of "horizontal" layout, the one with captcha is an inline type, the other is with columns where you define a global grid for the form.

Grid type:
image

Inline type:
image

With that in mind, how should we express these behaviors in xml?

I think we will need both inline type and columns. Columns we can define once at start something like

<form>
<columns count=3> <columns>

or better yet maybe

<form columns=3>

</form>

Or can we add a type grid with columns this way we can add the grid as a row anywhere

Youre right grid as an element would work great.
About inline i think a simple syntax should work:

<inline>
  <text>stuff</text>
  <input .../>
</inline>

This would make everything inside behave like a single item

Yes, this would be great. Also, It would be great if we can add support to override margins in this case at least left and right margins

Current layout system is flat and list based, where the DynamicForm is responsible for constructing the layout. This was designed with the list of class fields in mind, but it's terrible for richer custom layout and extensibility.

I built a new layout system which relieves the form from layout calculation and instead delegates it to whatever ILayout the current node implements. ILayouts can contain other ILayouts, so now we're recursive!

The logic is done for rows/cols, inline elements, stack panel and support for margins/alignment. What's left is to support recursive parsing inside XML..

The new API is completely internal and no visible change can be detected from outside the library, and even when it goes public it will be 100% optional and backwards compatible with no breaks.

XML will include it implicitly so expect it within the next 24 hours. The syntax will be like this:

<form>
  <row>
    <col width="1">
       <text>...</text>
       <input ... />
   </col>
    <col width="1" left="1">
       <text>...</text>
       <input ... />
       <row>
          <text>Inline text!!!</text>
          <action .... />
       <row>
   </col>
  </row>
</form>

in the grid above the width is divided in 3 parts, where column 1 takes 1/3, 1/3 in the middle is empty and col2 takes the remaining 1/3.

If a <row> contains non <col> children then it's treated as inline; one syntax to rule them all!

Another feature will be <layout>, which can specify layout settings for the wrapped children. For example:

<layout margin="16">
   <img src="..." />
</layout>

This will add a 16 margin to the img. This means that you are not limited to customizing margin to grid columns only.

I will post updates here

New parser is live, we should test it for bugs. The features are as mentioned above.

One breaking change is that actions are no longer inlined, so you have to group them in a <row>. This change has been done in order to allow fully flexible and recursive layouts with any kind of element.

When I have more time I will recreate one of the pictures you sent as a demo. I think the new layout is capable of handling those columns. Side note, is there support for multi line text fields? Do you need those in your project?

Yes, I will need to support multiline for things like comments.
This is great and thanks for doing it so quickly. I will try to implement this and let you know. I haven't pushed the dynamic forms feature to production so and breaking changes are welcome.

I am currently working on one of the forms. I think something that is missing on the XML parsing is the dropdown list and radio buttons.

Select is done:

<?xml version="1.0" encoding="UTF-8"?>
<form>
  <select from="type:Forge.Forms.Demo.Models.YesNo" name="NewsLetters" label="Do you want to receive weekly newsletters?" as="radiobuttons" nullable="true" />
  
  <select from="{ContextBinding Doctors}" name="AppointedDoctor" label="Appointed doctor" />

  <select from="type:Forge.Forms.Demo.Models.Gender?" name="Gender" label="Gender" />
  
  <select name="CustomSelection" defaultValue="second" as="radiobuttons" label="Select something">
    <option value="first">First Item</option>
    <option value="second">Second Item</option>
    <option value="third" name="Third Item" />
  </select>
</form>

Take note of the ? usage regarding enums and the nullable attribute. If your type is a nullable enum you will have options with a null included in the start. If you want to have nothing selected initially but you don't want a null option you use nullable="true". Like in the YesNo example I want two radio buttons but none selected initially.

image

While testing ensure that you give your fields a name because defaultValue won't work without it.

as can be of radiobuttons combobox comboboxeditable

It's quite close! The material design makes it look a bit more spaced apart. If you are looking for something more serious you can use the default WPF theme or maybe metro. Not all controls are implemented there but we can adapt it in a short time.

image

<?xml version="1.0" encoding="UTF-8"?>
<form>
  <title>Ultrasound</title>
  <row>
    <col width="2">
      <row>
        <text>Patient</text>
        <input />
      </row>
    </col>
    <col>
      <row>
        <text>Dob</text>
        <input />
      </row>
    </col>
  </row>
  <row>
    <col width="2">
      <row>
        <text>Age</text>
        <input />
      </row>
    </col>
    <col>
      <row>
        <text>Date</text>
        <input />
      </row>
    </col>
  </row>
  <row>
    <text>Right Kidney</text>
    <input />
  </row>
  <row>
    <text>Left Kidney</text>
    <input />
  </row>
  <row>
    <col>
      <row>
        <col>
          <text>Bladder</text>
        </col>
        <col>
          <row>
            <text>VOL</text>
            <input />
          </row>
        </col>
      </row>
      <row>
        <col>
          <text>Post Mkt.</text>
        </col>
        <col>
          <row>
            <text>VOL</text>
            <input />
          </row>
        </col>
      </row>
      <row>
        <col>
          <text>Prostate:</text>
        </col>
        <col>
          <row>
            <text>VOL</text>
            <input />
          </row>
        </col>
      </row>
      <row>
        <col>
          <text>Ureteric Jets:</text>
        </col>
        <col>
          <input />
        </col>
      </row>
      <br />
      <heading>Female</heading>
      <row>
        <col>
          <text>Uterus</text>
        </col>
        <col>
          <input />
        </col>
      </row>
      <row>
        <col>
          <text>Endromet.</text>
        </col>
        <col>
          <input />
        </col>
      </row>
      <row>
        <col>
          <text>Rt. Ovary</text>
        </col>
        <col>
          <input />
        </col>
      </row>
      <row>
        <col>
          <text>Left Ovary</text>
        </col>
        <col>
          <input />
        </col>
      </row>
    </col>
    <col>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/KidneyStructures_PioM.svg/590px-KidneyStructures_PioM.svg.png" />
    </col>
  </row>
  <row>
    <text>Comments</text>
    <input />
  </row>
</form>

One thing to note; you can only have one input inside an inline grouping... that's because the ActionPanel supports only one child to fill the remaining space. In the future it must be made possible to divide remaining space between fill elements.

Multi line text will be coming soon.

@edongashi this is great!

I have been using the material floating text boxes so it works fine.

I am not sure if there is an issue with the datetime control or I am doing something wrong. It is going out of alignment if you remove the icon.

<form>
  <title>ULTRASOUND WORKSHEET ABDOMEN</title>
  <heading>Patient details</heading>
  <row>
    <col width="2">
 
    
        <input type="string" name="FirstName" label="Patient name" tooltip="Enter your name here."  >
      <validate must="NotBeEmpty" />
   </input>
  
    </col>
    <col>
   
        <input type="datetime?" name="DateOfBirth" label="Date of birth" icon="calendar" conversionError="Invalid date string.">
      <validate must="NotBeEmpty" />
      <validate must="BeLessThan" value="2020-01-01">You said you are born in the year {Value:yyyy}. Are you really from the future?</validate>
   </input>
 
    </col>
  </row>
  <row>
    <col width="2">
     <input type="datetime?" name="AppointmentDate" label="Date" icon="calendar" conversionError="Invalid date string." /> 
    </col>
    <col> 
         <input type="string" name="Age" label="Age" > 
   </input> 
    </col>
  </row>

 <heading>Findings</heading>

  <row>
        <input type="string" name="RightKidney" label="Right Kidney" ></input>
  </row>
  <row>
    <input type="string" name="LeftKidney" label="Left Kidney" ></input>
  </row>
  <row>
    <col>
      <row>
        <col>
          <text>Bladder</text>
        </col>
        <col>
          <input type="string" name="BladderVol" label="Bladder Vol (mm)" ></input>
        </col>
      </row>
      <row>
        <col>
          <text>Post Mkt.</text>
        </col>
        <col>
          <row>
            <text>VOL</text>
            <input />
          </row>
        </col>
      </row>
      <row>
        <col>
          <text>Prostate:</text>
        </col>
        <col>
          <row>
            <text>VOL</text>
            <input />
          </row>
        </col>
      </row>
      <row>
        <col>
          <text>Ureteric Jets:</text>
        </col>
        <col>
          <input />
        </col>
      </row>
      <br />
      <heading>Female</heading>
      <row>
        <col>
          <text>Uterus</text>
        </col>
        <col>
          <input />
        </col>
      </row>
      <row>
        <col>
          <text>Endromet.</text>
        </col>
        <col>
          <input />
        </col>
      </row>
      <row>
        <col>
          <text>Rt. Ovary</text>
        </col>
        <col>
          <input />
        </col>
      </row>
      <row>
        <col>
          <text>Left Ovary</text>
        </col>
        <col>
          <input />
        </col>
      </row>
    </col>
    <col>
      <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/KidneyStructures_PioM.svg/590px-KidneyStructures_PioM.svg.png" />
    </col>
  </row>
  <row>
    <text>Comments</text>
    <input />
  </row>
</form>

image

if you remove the icon i.e. icon="calendar" then it shows like the following image

image

I am not 100% sure if I understand your problem.

If you add an icon to a control it will push it to the right. If you want to have your control pushed without an icon you can make its icon icon="none". With this the space is reserved in order to align with controls that have icons.

Ah now I noticed the problem. Hmm it must be some margin misalignment. I have to check if the text field and calendar field have the same margins paddings etc.

Yea I just checked with icon=none, but that doesn't fix this. I think you might have to look into verticalcontentalighnment of the datetime control maybe

You're right calendar had alignment=Bottom, not sure why. I changed it to center, could you veirfy if it's fixed?

Edit: Also pull the margin fix commit and I think it should be fine now

Edit2: It's icon="empty" if you want to add space without showing any icon:

image

Yes, it works well now.

I think you might have to change the margin to -2 from -1

<DatePicker
                Name="ValueHolderControl"
                Grid.Column="1"
                Margin="0,-2,0,0"
                VerticalAlignment="Center"
                internal:FocusHelper.InitialFocus="{formBuilding:FormBinding InitialFocus}"
                internal:SelectTextOnFocus.Active="{formBuilding:FormBinding SelectOnFocus}"
                materialDesign:HintAssist.Hint="{formBuilding:FormBinding Name}"
                SelectedDate="{formBuilding:FormBinding Value}"
                Style="{StaticResource MaterialDesignFloatingHintDatePicker}"
                ToolTip="{formBuilding:FormBinding ToolTip}" />

Done, let's just hope that this offset is not a function of FontSize, because different fields may move differently on font size change and it would be a mess. Ensuring that invariance is up to MD toolkit. But anyway, if you encounter any other problem let me know.

This is all working well.

Did you manage to add support for multi line textbox?

It's almost done, which style should we choose?

image

Done, I liked the box better so I made that the default. We can probably add a property to indicate which style should be used.

XML syntax is identitic to input, except you use textarea tag:

<textarea name="..." label="..." .... />

Thanks. Yes, box style is good. I will give this a go.

How do you set SelectionType.ComboBoxEditable in the select. Basically, I want to give the user the option to type the text if the value is not in the list

<select name="CustomSelection"   label="Select something">
    <option value="first">First Item</option>
    <option value="second">Second Item</option>
    <option value="third" name="Third Item" />
  </select>

as="comboboxeditable"

A warning, if you make the combobox editable it will disregard option values. It will give provide the textual value as is.

For example, in this demo:

<form>
    <select name="CustomSelection" as="comboboxeditable" label="Select something">
    <option value="first">First Item</option>
    <option value="second">Second Item</option>
    <option value="third" name="Third Item" />
  </select>
</form>

when I select a value and serialize it to json I get this:

{
  "CustomSelection": "Second Item"
}

instead of second

Yes, this is correct and the expected outcome for me. Whatever is in the textbox should be the returned value.