We are investigating how to create data entry views from a dynamic list of pre-defined field definitions. By "pre-defined", I mean that there are only 8 basic field types. The Silverlight Toolkit's DataForm control is almost what want, but it targets object properties (not a list of custom definitions).
Is there an existing project to make this easy? Please comment on my design idea (below). I have only ~2 weeks Silverlight experience.
Basic design idea:
I am thinking of defining custom data field types. An IEnumerable<BaseDataField> will be received by the UI, enumerated, and controls will be created based on the type of each field. Each field will create a label with the description and BooleanDataField will create a CheckBox, LookupDataField will create a ComboBox, etc.
Pseudo code to clarify the idea:
public abstract class BaseDataField {
public string FieldCode { get; private set; }
public string FieldDescription { get { return FieldDefinitions.Instance.FieldDescription(FieldCode); } }
...
}
public class StringDataField : BaseDataField
public class BooleanDataField : BaseDataField
public class CurrencyDataField : BaseDataField
public class IntegerDataField : BaseDataField
public class NumericDataField : BaseDataField
public class DateTimeDataField : BaseDataField
public class LookupDataField : BaseDataField
public class SpecialDataField : BaseDataField
This will be extended to make the fields bindable; allow specifying custom controls for each type; and have validation feedback.
Can it be easily done in Silverlight or should we create a custom control?
Note: This programme will be a web UI for an existing, multi-tier LOB platform. All data is serialised from a JSON-based REST service.
Thanks!
I would highly recommend incorporating the DataForm class into your solution and it is indeed capable of handling custom controls. I've been chasing this goal of an ideal minimal-xaml data form for years and I have finally settled on what I think is a great combination of the built-in functionality of DataForm and custom fields derived from DataField. For example, I just extended DataField so that when no Content property is specified, it automatically uses a TextBox which minimized my XAML big time. I also added support for the DisplayFormatAttribute which DataForm doesn't seem to support natively.
Anyhow, what you could do first is create a bunch of subclasses like you describe above and derive from DataField. Then in the OnApplyTemplate method, create an element to represent the data. For example:
class BooleanDataField : DataField {
protected override void OnApplyTemplate() {
if (this.Content == null) {
var check = new CheckBox();
check.SetBinding(CheckBox.IsCheckedProperty,
new Binding(this.PropertyPath));
this.Content = check;
}
base.OnApplyTemplate();
}
}
As for how to get your field metadata loaded by DataForm, you have a few options. You could handle the AutoGeneratingField event and look up the appropriate field to use on the fly or you can disable auto field generation and just load it up with your own.
We started with DataForm, but ended up overriding everything that makes it useful and still had some problems. I learned a great deal from the attempt and eventually built a custom control for the job. Unfortunately this solution is not very extensible...
Related
I want to run through a series of steps that comprise a complete test. Some of these steps are automatic (so informational) and others require user interaction. Test steps are not known at compile time, they are using MEF to be loaded.
Currently I have something like
public abstract class TestRunnerBase
{
public abstract void Run();
}
With a list of steps like this:
List<TestRunnerBase> Steps = new List<TestRunnerBase>();
So all data representing a test serializable and that works okay so far. However what I really need is for a user to load a test from XML, it then walks them through the options displaying information on screen and gathering results.
But trying to work out how to create a control for data that is unknown at compile time has ended up with me getting a bit stuck on the best approach.
I am thinking to do this I would have a list of custom controls (1 a step) and the GUI would display the first step, wait for that control to be complete (I was thinking here that a raised event might work?) and then display the next if available and so on until the test is complete.
So is it possible to do this in WPF? Can you create a stack of controls in WPF that can each raise the same event to the parent container or is there a better way to do it?
But if I also use the abstract class I can't then derive a control from it also as no multiple inheritance in C# of course.
I would use MVVM and create a viewmodel that understood how to navigate the list of steps, providing a wizard type structure (prev/next) and exposing the current step.
I assume that while you have different kinds of potentially unknown steps that you have a concrete set of input options (bool, text, date, int, etc) then you could use a abstract property on your TestRunnerBase that identifies what kind of input is required (or none) using an enum that must be overriden.
Then you could use datatemplates and/or data triggers to control what is shown for each step of the test. The main viewmodel could check that conditions are right for going to the next step (perhaps a validate on your test).
Some psuedo code to get you thinking:
public enum TestInput
{
None,
Bool,
Text
}
public abstract class TestRunnerBase
{
public abstract TestInput TestInput { get; }
public bool BoolInput { get; set; }
public string TextInput { get; set; }
public abstract bool CanRun()
public abstract void Run();
}
public class MainViewModel
{
List<TestRunnerBase> Steps = new List<TestRunnerBase>();
public TestRunnerBase CurrentStep {get;set;};
public MainViewModel()
{
//loads the Steps
CurrentStep = Steps
}
public Command RunStepCommand
{
if (CurrentStep.CanRun())
{
CurrentStep.Run();
CurrentStep = Steps.Next(); //you get the idea
}
}
}
For your XAML you would bind a ContentPresenter to CurrentStep and use a datatemplate (and maybe data triggers) to control what is visible to the user (and of course bound to the UI).
In WPF you can dynamically create controls using XAML. Simply create a XAML snippet with the layout you want (either programatically or by hand) and use XamlReader.Parse to create the entire tree from the snippet. The returned object can then be inserted somewhere in the visual tree of your window.
To generate events from the visual tree generated by XamlReader you can use routed events.
This is an example of my general problem:
I have different implementations of ITextSearcher to search for something. Each implementation has different settings that can be edited by the user. So I can't make a general editable view for the implementations (because the settings can't be abstracted by an interface).
I have developed a simple library that helps in this situations. It allows to declaratively tag a class and it's properties with view information. A view generator uses this information to render the class. This is an example:
[Editable]
internal class TermSearcher : ITextSearcher
{
[Editable(Name="Search Expression", Order = 1)]
public string Expression
{...}
[Editable(Name="Match Similar Characters", Order = 2)]
public bool MatchSimilarChars
{...}
// rest of the implementation ...
}
Is there a better solution?
Yes. In WPF or Silverlight you can create a DataTemplate for the various instantiations of ITextSearcher. You can then use a DataTemplateSelector to pick the appropriate DataTemplate given an instance of an ITextSearcher
Since you tagged the post with MVVM I'm assuming you are using WPF
I've got a WPF MVVM application. One of my views has a user control that needs to be customizable for each installation. It's basically a sketch of the customers installation with some labels etc. bound to a viewmodel.
Now my problem is that this user control is different on each site/installation. One approach is to load the xaml from a file/database runtime using a xaml reader. This works but since my viewmodel is generic I have to bind to methods instead of properties and I can't load a xaml with objectdataprovider.
Currently I'm trying to see if MEF can be used so that I can create the user control as a plug-in. So what I'm looking for now is this:
how can I define a user control with view/view model that exports a contract for MEF
How can my parent view (in my wpf app) load the imported user control
Any tips are appreciated, or maybe someone has a different approach?
I suggest you look into Prism in combination with MEF. It has a notion of Modules (plug-ins in your case) and Regions (mechanism of dynamically loading views).
You will be able to export a view using a simple attribute:
[ViewExport(RegionName = RegionNames.MyRegion)]
public partial class MyView : UserControl {
public MyView() {
this.InitializeComponent();
}
[Import]
public MyViewModel ViewModel {
set { DataContext = value; }
}
}
[Export]
public class MyViewModel : ViewModelBase
[
...
}
And in your main application XAML you will be able to import the plugin's views like this:
<ContentControl Regions:RegionManager.RegionName="{x:Static Infrastructure:RegionNames.MyRegion}"/>
One thing I'd consider is the design where you need to install a custom View for each installation. Instead, I'd look to make that View more generic. This will make your design more simple in the long run. Plus, you are setting up for a maintenance nightmare with a different installation for every installed base.
It's a little difficult to tell from your description, but it sounds like the View is a collection of some kind of an object (some kind of drawing with a label or something). Therefore, I'd treat it as such.
I'd create a base abstract class that describes what every object that your View could show. Since I don't have more information, I'll call this thing a "DrawingObject" for lack of a better term. This class would hold all information common to all objects in your View. Note that ObservableItem is a class that implements INotifyPropertyChanged, and SetProperty sets the value in that base class and raises PropertyChanged.
abstract class DrawingObject : ObservableItem
{
Point mPosition;
public Point Position
{
get { return mPosition; }
set { SetProperty("Position", ref mPosition, value); }
}
String mLabelText;
public String LabelText
{
get { return mLabelText; }
set { SetProperty("LabelText", ref mLabelText, value); }
}
}
Then, derive more custom objects from that base class:
class Counter : DrawingObject
{
public Counter() : base()
{
}
}
Your ViewModel would then just have a collection of these objects, using the base class. The set may be private, because you will probably get the objects from someplace in the constructor (i.e. the database, or a flat file, or...)
class ViewModel : ObservableItem
{
public ViewModel() : base()
{
// Call something to populate DrawingObjects property
PopulateDrawingObjects();
}
ObservableCollection<DrawingObject> mDrawingObjects =
new ObservableCollection<DrawingObject>();
public ObservableCollection<DrawingObject> DrawingObjects
{
get { return mDrawingObjects; }
private set { mDrawingObjects = value; }
}
}
Then, your View would bind to this collection and draw them appropriately (I'll leave that as an exercise for the implementer).
One extension that I didn't show is that the DrawingObject may need to implement the appropriate serialization functionality.
Obviously, this is a rough sketch of the design, and may have a couple of errors (I did it from my head), but hopefully it's enough to go on.
I am setting the datasource of a control at runtime to BindingList which works fine.
But I would prefer if I could see this in the designer which would mean that I could select the datamember properties at design time, rather than having to set them at runtime also.
What is the minimum requirements to allow this?
Well you will have to do the following:
Add New Data Source ( and you can select your class object)
when you bind to grid or any user control, it will create BindingSource (or you can use existing one).
You can then bind your BindingList
As long as your class has public {get; } properties stuff it will be visible in Data Source and in your Grid (as columns for example)
See MDSN link here
Although the answer from anvarbek raupov is simplier, what I ended up doing was talking an existing working class which implemented a few unnessary interfaces and ripping bits out until I got down to something like the following.
[ToolboxItem(true)]
public class myClassList : BindingList<myClass> , IComponent
{
public event EventHandler Disposed;
public ISite Site { get; set; }
public void Dispose()
{
}
}
I'm displaying Business Object in generic DataGrids, and I want to set the column header through a custom attribute, like:
class TestBo
{
[Header("NoDisp")]
public int ID {get; set;}
[Header("Object's name")]
public String Name { get; set; }
}
So far, so good, but I'd also want to separate my display from my data, by inheritance:
class TestBO
{
public int ID {get; set;}
public String Name { get; set; }
}
class TestPresentationBO : TestBO
{
//Question: how to simply set the Header attribute on the different properties?
}
I see a solution via reflection with a SetCustomAttribute in the Child constructor, but it will be cumbersome, so is there a simple and elegant trick for this problem?
Please prevent me from breaking the data/presentation separation ;o)
Question: how to simply set the Header attribute on the different properties?
There is no way to set an attribute on an inherited member the way you have suggested, since attributes are specific to a type. SetCustomAttribute won't help you - it's only any good when you construct new types at runtime. Once an attribute has been compiled in you cannot change it at runtime, since it's part of the metadata.
If you want to maintain the separation you will have to find another way.
(You could make the properties virtual, override them in the Presentation class and add attributes on the overrides, but this looks dodgy and doesn't really separate anything - you end up with a complete TestBO class in your TestPresentationBO anyway...)
Make the properties in TestBo virtual and override them in TestPresentationBO. That way you can add the attributes.
Just thinking, can't you solve this with partial classes and the MetadatatypeAttribute? MVC2 uses this pattern for Model validation.
You can do it like WCF RIA Services. Add an attribute to TestBO, like [Presentation] taking a type as parameter. This new type will redefine the properties, but with the presentation attributes.
At run-time, you have to get the identity of the new type and get the custom attributes of its properties.
Or forget about the attribute and have a dictionary mapping the BO with the presentation BO class. This presentation BO class does the same thing as above, i.e. redefine properties with custom attributes.
the presentation BO class is never instantiated, it is simply reflected upon to get presentation info.
Are you using the MVVM (model view view-model) pattern? It seems to me, and partly from the other answers, that you can't really do this with the custom attributes like you want. But, it also seems to me that your TestPresentationBO is really just like a "View Model" for TestBO. A view model is basically a sort of wrapper or surrogate for a business or logic class--which is basically what you want. (This summary of a view model may not be 100% accurate; I'm just starting out with MVVM myself.)
You can create a TestBOViewModel to wrap TestBO, then pass the collection of TestBOViewModel to the datagrid. Of course, you can decorate the properties exposing the wrapped class with [Header("Object's name")] etc. This doesn't use inheritance, but I don't see why you'd need to use inheritance in this situation. Using a view model, does, however, cleanly separate your presentation (view) from your data (model) by using the wrapper (view model).
For more info on the MVVM pattern, I found this to be an interesting read: WPF Apps With The Model-View-ViewModel Design Pattern.
Something like this. Of course, you can add validation and other goodies in here too.
public class TestBOViewModel // extend from DependencyObject
{ // if you want to use dependency properties
private TestBO _myBO;
public TestBOViewModel(TestBO bo)
{
_myBO = bo;
}
[Header("NoDisp")]
public int ID
{
get { return _myBO.ID; }
set { _myBO.ID = value; }
}
}
For C# 6.0 you can easily hide inherited members and introduce your own attributes. This might, however, hide any attributes on the original property. Also this simplified syntax makes the property read-only, so you might need to pipe the get/set yourself.
public class User
{
public string Login { get; set; }
}
public class UserDetail : User
{
[Display(Name = "Login:")]
public new string Login => base.Login;
}