Closed redbaty closed 6 years ago
It is possible to register custom hooks in FormBuilder
. They will get called for each property, where you can decide if you want to return something custom, or null if you want to pass to other builders.
There are two types of builders PropertyBuilders
and TypeBuilders
. PropertyBuilders are queried first to see if the property has any attribute that modifies the generated field. If no such builder takes responsibility then TypeBuilders
are checked, which return fields for types like string
and boolean
.
The idea is that you can hook builders when you encounter an attribute such as SelectFrom
in PropertyBuilders
, while you can hook controls for custom types in TypeBuilders
.
A SelectFromBuilder
is implemented, but the control is not yet implemented. I think I can do that quickly.
The syntax is as follows:
[SelectFrom(new string[] { "Designer", "Engineer", "Manager" })]
public string Occupation { get; set; }
or
[SelectFrom("{ContextBinding Occupations}")] // Gets the list from datacontext
public string Occupation { get; set; }
or
[SelectFrom("{Binding Occupations}", SelectionType = SelectionType.RadioButtons]
public string Occupation { get; set; }
public string[] Occupations => new string[] { "Designer", "Engineer", "Manager" };
etc.. check SelectFromAttribute.cs
for attributes. I will tag you when I get the time to implement the control.
All right then! Anyway I'd like to congratulate you for your work so far, this is really amazing and I'm really surprised this is not being talked about in the "WPF-UWP" community, keep up the good work! 😄
I really wish I could help, but I am not that familiar with the MVMM and this project seems a bit over-complicated with the Old stuff, if you could do a Diagram showing how stuff are made it would be pretty neat.
Thanks! :smile:
I agree that without proper documentation it can be a bit complex to dig into the internals. Let me try to write a brief explanation, which I can copy to a doc file later.
The MaterialForms
project has 4 folders:
Old
folder and Resources
folder - These contain the old version of the library. The API was stiff and hardcoded in so many places, which caused a lot of issues like no localization, no theming, no XAML support, hosted within predefined containers (dialogs and windows) and so many more. So basically ignore these folders, as they are there for backwards compatibility,
The hot new stuff is located in Wpf
and Themes
.
Themes
contains XAML resource dictionaries for 3 form styles: WPF, Metro and Material. So for example when I want to add a combobox I need to consider all 3 styles for uniform support.
Form building and logic is located in Wpf
folder. The philosophy of creating forms has shifted from declaring a bunch of Schema
s to rich classes and annotations. I thought this approach was true to MVVM, plus you could reuse your domain objects by adding unobtrusive annotations.
Forms are built as follows: A FormBuilder
takes a type of instance of whatever and returns a IFormDefinition
. This IFormDefinition
is the blueprint for generating forms.
<DynamicForm Model="{Binding Model}">
will get some object or type in Model
prop and display a MVVM style bound form for that object. You can specify a FormBuilder
property or FormBuilder.Default
is used by default.
Form definitions can contain FormElement
s. They can be anything, including text, headers, icons, images, and most importantly editable fields: DataFormField
.
As mentioned above FormBuilder
is designed to be modifiable even in runtime. You can register custom behavior for example if I do an Image
annotation
[Image]
public string MyImage { get; set; }
I can hook a PropertyBuilder
to check if the property contains an ImageAttribute
, if yes, then we can intercept and create a custom control instead of the default text box. That control can then show a WPF Image with src="{FormBinding Value}"
.
Similarly, if we have a custom type for example a RoomReservation
with DateTime Date
and int NumberOfGuests
public RoomReservation ReservationDetails { get; set; }
We can hook a TypeBuilder
for type RoomReservation
so that we can return a custom control whenever this property type is encountered in a class.
FormBinding
s can be imagined as "delayed bindings", since you won't know in compile time what field you will be binding to. For example {FormBinding Value}
would resolve to a {Binding MyImage}
when the form definition is actually compiled.
Cool features resulting from the model approach:
ModelState.Reset(model); // reset model to default value, you can define custom defaultvalues in [Field(DefaultValue = "...")]
ModelState.Validate(model); // check if model satisfies validators
ModelState.UpdateFields(model); // refresh view without INotifyPropertyChanged
One issue requested XML support, and it was surprisingly easy to implement (like 2 hours of work) due to how well it's structured.
Another crazy idea that comes to mind is the possibility generating generic CRUD routes with EntityFramework's .Set<T>
, display a DataGrid for read, and you can generate a dynamic form for Insert and Update. Maybe even a details page if you render all fields readonly.
I appreciate your issues that request features, because these give me a vision on what to focus for my limited time (I wish I could have been more active in this project).
@redbaty I have added combobox support. Check out Selection
and FoodSelection
demos. There were quite a few cases to consider (may have bugs), but the feature has come out great!
@EdonGashi Awesome dude! Really good job, will test it right now 👍
Hey it would be nice to update the nuget package, but anyway I'm closing this for now, great work 😄
Oh and don't know if it was intentional but it won't support .NET 4.5 on the preview package
No clue about the versions... PRs with fixes are appreciated!
I thought I had appveyor setup to publish NuGet pre-releases. I'll check that quickly
@EdonGashi I just checked it the target framework has been updated since the last stable nuget relase, but it's all right since 4.6 is supported on every platform 4.5 is supported in
@EdonGashi Hey actually I've seen that it works on your demo but on my project it still won't work, I'm debugging it right now.
I reconfigured publishing from AppVeyor. Could you check 2.0.0-ci20? Since we are in pre-release territory we can test things out
Yeah still not luck, I'll post the code that I'm using so if you want too you can debug it too.
XAML
<Button Content="Adicionar movimentação rápida"
Command="{x:Static materialDesign:DialogHost.OpenDialogCommand}"
Style="{DynamicResource MaterialDesignFlatButton}" Foreground="White">
<Button.CommandParameter>
<controls:DynamicForm Model="{Binding Selection}"></controls:DynamicForm>
</Button.CommandParameter>
</Button>
Model
class HomeModel
{
public FoodSelection Selection { get; set; } = new FoodSelection();
}
public class FoodSelection : INotifyPropertyChanged
{
private string firstFood = "Pizza";
private string secondFood = "Steak";
private string thirdFood = "Salad";
private string yourFavoriteFood;
[Field(DefaultValue = "Pizza")]
[Value(Must.NotBeEmpty)]
public string FirstFood
{
get => firstFood;
set
{
firstFood = value;
OnPropertyChanged();
}
}
[Field(DefaultValue = "Steak")]
[Value(Must.NotBeEmpty)]
public string SecondFood
{
get => secondFood;
set
{
secondFood = value;
OnPropertyChanged();
}
}
[Field(DefaultValue = "Salad")]
[Value(Must.NotBeEmpty)]
public string ThirdFood
{
get => thirdFood;
set
{
thirdFood = value;
OnPropertyChanged();
}
}
[Text("You have selected {Binding YourFavoriteFood}", InsertAfter = true)]
[SelectFrom(new[] { "{Binding FirstFood}, obviously.", "{Binding SecondFood} is best!", "I love {Binding ThirdFood}" })]
public string YourFavoriteFood
{
get => yourFavoriteFood;
set
{
yourFavoriteFood = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Result
Reproduced, my guess is that a resource dictionary reference is missing somewhere. Shouldn't be too difficult to find.
quick note: comboboxes will work only with material theme atm
@EdonGashi even if you have already found the solution please don't commit it yet, I'd like to help you out on this one
Found it! That's no bug, I totally forgot that by default WPF theme is used. You can notice by the textbox labels :)
Add this reference for Material Theme
https://github.com/EdonGashi/WpfMaterialForms/blob/master/src/Demo/MaterialForms.Demo/App.xaml#L19
@EdonGashi LOL, I was typing the comment! You beat me to it 😄
@EdonGashi Hey you should work on documenting the more complex stuff like the IFieldBuilder, I'll send a PR documenting the new stuff but I think it would be best for you to document the more complex ones
Alright, I will gradually document everything. Thanks!
It would be nice to be able to manually add some custom controls, like a combobox.