I create a form in run-time from some parameters in a list of fields.
List<Fields> lstFields = new List<Fields>()
{
new Fields(){ FieldType = Fields.fieldTypes.INPUT, Info = "Some Info", Label = "first", Mandatory= true},
new Fields(){ FieldType = Fields.fieldTypes.CHK, Info ="Some Info", Label="Second",
Items = new List<String>(){"item1","item2","item3","item4"} },
new Fields(){ FieldType = Fields.fieldTypes.INPUT, Label = "Name", Mandatory= true},
new Fields(){ FieldType = Fields.fieldTypes.INPUT, Label = "Surname", Mandatory= true},
new Fields(){ FieldType = Fields.fieldTypes.COMBO, Label = "City", Mandatory = false,
Items = new List<String>(){"item1","item2","item3","item4"}}
}
I create my fields in a foreach statement:
foreach (Fields fd in lstFields)
{
[...]
switch (fd.FieldType)
{
case Fields.fieldTypes.INPUT:
TextBox currentTB = new TextBox(); //It violates MVVM pattern :(
content.Add(currentTB);
[...]
break;
[...]
default:
break;
}
}
}
I need a form validation strategy. All the strategies that I know are based on the binding. The problem is that I can not bind the property because I create the controls dinamically. I would like to solve the problem following the MVVM design pattern.
You say you want to solve it using MVVM yet you're flagrantly violating it already by creating view elements in code. What you should be doing is creating view models to represent the GUI items you want to create, displaying them in an ItemsControl and using a combination of DataTemplates and Triggers to automatically create the view controls for you. By doing that you ensure that everything is data bound and that you adhere properly to MVVM; data validation is then done as it is in any other MVVM app.
As it turns out I answered a question just the other day and posted code showing exactly how to do this.
Related
I wrote some example code to demonstrate my question. one is bound to Object and other to DataRow:
Bind to DataRow example:
namespace WindowsFormsApplication1
{
public partial class frmBindExample : Form
{
public frmBindExample()
{
InitializeComponent();
InitForm();
}
private void InitForm()
{
//;; Init the list
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("Id"));
dt.Columns.Add(new DataColumn("Name"));
dt.Rows.Add(new string[] { "5476", "Smith" });
dt.Rows.Add(new string[] { "5477", "Marlin" });
Label label1 = new Label() { Top = 130, Left = 10, Text = "Id of Smith is:" };
this.Controls.Add(label1);
//;; Bind two direction with TextBox.
TextBox textbox1 = new TextBox() { Top = 130, Left = 130, Width = 100 };
this.Controls.Add(textbox1);
textbox1.DataBindings.Add("Text", dt.Rows[0], "Id");
//;; The binding system respose changing property value
Button button1 = new Button() { Top = 160, Left = 10, Width = 200, Text = "Set Id=99 Directly by property" };
this.Controls.Add(button1);
button1.Click += (s, e) =>
{
dt.Rows[0]["Id"] = "99";
};
DataGridView dg = new DataGridView() { Top = 200, Left = 10 };
this.Controls.Add(dg);
dg.DataSource = dt;
}
}
}
It's look like:
As you can see, the binding to TextBox not work as it on next example. But, when I update the field by pressing on the button, the data grid refresh immediately:
Ok, now take look what hempen if I bind Object instead:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class frmBindExample : Form
{
public frmBindExample()
{
InitializeComponent();
InitForm();
}
private void InitForm()
{
//;; Init the list
List<Person> lst = new List<Person>();
lst.Add(new Person() { Id = "5476", Name = "Smith" });
lst.Add(new Person() { Id = "5477", Name = "Marlin" });
Label label1 = new Label() { Top = 130, Left = 10, Text = "Id of Smith is:" };
this.Controls.Add(label1);
//;; Bind two direction with TextBox.
TextBox textbox1 = new TextBox() { Top = 130, Left = 130, Width = 100 };
this.Controls.Add(textbox1);
textbox1.DataBindings.Add("Text", lst[0], "Id");
//;; The binding system respose changing property value
Button button1 = new Button() { Top = 160, Left = 10, Width = 200, Text = "Set Id=99 Directly by property" };
this.Controls.Add(button1);
button1.Click += (s, e) =>
{
lst[0].Id = "99";
};
DataGridView dg = new DataGridView() { Top = 200, Left = 10 };
this.Controls.Add(dg);
dg.DataSource = lst;
}
}
//;; The person class can bind any his property without any extra call fo change detection
public class Person
{
public string Id { get; set;}
public string Name { get; set; }
}
}
Now, the TextBox show the Id value as we aspect. But push on the Set button not refresh data on the DataGrid.
So, my question is:
Why binding TextBox not work correctly on the first example?
It's true to say, that automatic (without any extra call for do binding) propagate update from source to control happens only with DataRow?
Why binding TextBox not work correctly on the first example?
It's because the TypeDescriptor of the DataRow doesn't have a Id property. Consider the following rules:
When data binding to a property of an item, the type descriptor of the item should contain a property with that name.
When data binding to a list, the list item type descriptor should contain a property with that name.
Is it true to say, that automatic (without any extra call for do binding) propagate update from source to control happens only with DataRow?
No. It's not because of DataRow type. It's because of INotifyPropertyChanged and IBindingList. Consider the following rules:
When data binding a control to an item, if the item implements INotifyPropertyChanged, then UI will be updated immediately after updating item.
When data-binding to a list control, if the item Implements INotifyPropertyChanged and the list implement IBindingList, the UI will be updated immediately after updating item or list.
More information
What I described above in short, you can find in details in Windows Forms Data Binding. I recommend reading the following useful documents:
Data Sources Supported by Windows Forms
Interfaces Related to Data Binding
Change Notification in Windows Forms Data Binding
Since, maybe, describing this procedure in the comments in not a great idea, here's the enlarged version.
Define a BindingList or a List<T> as the data storage object (I prefer a BindingList, but a simple List, here, will do the job anyway).
Define a BindingSource that will provide change notifications and currency management. It greatly simplifies the binding of controls in WinForms (the class object should implement INotifyProeprtyChanged, but for the purpose of this example is not important. It may become important in more specific scenarios, when you have two-way bindings that need to update the UI and the source of data immediately).
Set the BindingSource.DataSource property to the object that provides the data: here, a BindingList or an IList.
Add a Binding to the TextBox.Text property which will be bound to a property of the Data source (or a Column of a DataTable, for example), setting the DataSource value of the Binding to the BindingSource and the DataMember value to the Property (or Column) of the data source to which the TextBox property is bound. Here, the Id property of the Input class.
Subscribe to the Parse event of the TextBox Binding, to provide means to validate the data entered in the TextBox before allowing an update of the Data source. If the value entered doesn't fit the description (i.e., a user entered letters instead of numbers), we can call, for example, the BindingSource.ResetCurrentItem method to cancel the data update
Set the DataGridView.DataSource to the BindingSource.
This is what happens, using the code shown here:
Note:
I'm using a lambda here to subscribe to the Parse event; you may want to use a separate handler if you need to subscribe/unsubcribe to this event more than once.
internal class Input
{
public int Id { get; set; }
public string Name { get; set; }
}
internal List<Input> inputData = new List<Input>();
internal BindingSource bindingSource;
private void button1_Click(object sender, EventArgs e)
{
bindingSource = new BindingSource();
inputData.AddRange(new [] {
new Input() { Id = 5476, Name = "Smith" },
new Input() { Id = 5477, Name = "Marlin" }
});
bindingSource.DataSource = inputData;
Binding tboxBind = new Binding("Text", bindingSource, "Id", false, DataSourceUpdateMode.OnPropertyChanged);
tboxBind.Parse += (pObj, pEvt) =>
{
if (!int.TryParse(pEvt.Value.ToString(), out int value))
bindingSource.ResetCurrentItem();
};
textBox1.DataBindings.Add(tboxBind);
dataGridView1.DataSource = bindingSource;
}
I have created a data template to use within a list view. This will later be expanded to add more content to each item in this list view. At the moment all the items that are bound to the observable collection are working as expected, except for one.
In each instance of the data template the bound properties are height, RouteName and routeStops. The height and RouteName are working fine but I'm not sure how to bind the routeStops correctly.
For each one of the RouteNames there are multiple stops, so for each data template use there must be one label that has the RouteName and multiple labels for each stop on the route (using routeStops).
I am not entirely sure how to achieve this, I can only seem to bind one stop to one label. I want to create them dynamically to allow for any amount of stops.
So the code behind that creates the data template (Just the constructor):
public MainRoutePageViewDetail(MessagDatabase database)
{
InitializeComponent();
BindingContext = mainroutepageviewmodel = new MainRoutePageViewModel(database,Navigation);
StackLayout mainstack = new StackLayout();
var routelisttemplate = new DataTemplate(() => {
ViewCell viewcell = new ViewCell();
stacklayout = new StackLayout();
stacklayout.SetBinding(StackLayout.HeightRequestProperty,"height");
viewcell.View = stacklayout;
// labels for template
var nameLabel = new Label { FontAttributes = FontAttributes.Bold, BackgroundColor = Color.LightGray };
nameLabel.HorizontalOptions = LayoutOptions.Center;
nameLabel.VerticalOptions = LayoutOptions.Center;
nameLabel.SetBinding(Label.TextProperty, "RouteName");
//inforLabel.SetBinding(Label.TextProperty, "Stops");
stacklayout.Children.Add(nameLabel);
StackLayout nextstack = new StackLayout();
var nameLabel2 = new Label { FontAttributes = FontAttributes.Bold, BackgroundColor = Color.Red };
nameLabel2.HorizontalOptions = LayoutOptions.Center;
nameLabel2.VerticalOptions = LayoutOptions.Center;
nameLabel2.SetBinding(Label.TextProperty, "routeStops");
nextstack.Children.Add(nameLabel2);
stacklayout.Children.Add(nextstack);
return viewcell;
});
ListView listviewofroutes = new ListView();
mainstack.Children.Add(listviewofroutes);
listviewofroutes.SetBinding(ListView.ItemsSourceProperty, "routeLabels");
listviewofroutes.ItemTemplate = routelisttemplate;
listviewofroutes.HasUnevenRows = true;
Content = mainstack;
}// end of constructor
This is bound to an ObservableCollection in the view model. Im going to leave this out as its irrelevant because the bindings work fine.
This calls down to functions in the model that collect data from SQL tables.
The function in the model that collects data:
public List<RouteInfo> getrouteInfo()
{
var DataBaseSelection = _connection.Query<RouteInfoTable>("Select * From [RouteInfoTable]");
List<RouteInfo> dataList = new List<RouteInfo>();
for (var i = 0; i < DataBaseSelection.Count; i++)
{
var DataBaseSelection2 = _connection.Query<RouteStopsTable>("Select StopOnRoute From [RouteStopsTable] WHERE RouteName = ? ",DataBaseSelection[i].RouteName);
dataList.Add(new RouteInfo
{
ID = DataBaseSelection[i].ID,
RouteName = DataBaseSelection[i].RouteName,
Stops = DataBaseSelection[i].Stops,
DayOf = DataBaseSelection[i].DayOf,
IsVisible = DataBaseSelection[i].IsVisible,
routeStops = DataBaseSelection2[i].StopOnRoute,
height = 200
});
}
return dataList;
}
The first table (RouteInfoTable) gets RouteName and some other information and the second table gets the stops on the route using the RouteName as a key. This is all added to a list of RouteInfo instances.
DataBaseSelection2 grabs all of the stops on the route but only one of them displays. I know why this is but I dont know how to display all three.
The Table definitions and class definitions as well as the selections from the tables are not an issue. I have debugged these and they are getting the correct information I just dont know how to display it on the front end in the way I want to. Here is a visual of what I mean if its getting complicated:
The best I can do is one route stop not all three.
An ideas how to achieve this?
Sorry if its complicated.
You can use Grouping in ListView to achieve this visual. Basically in this case you will be defining two DataTemplate(s) -
GroupHeaderTemplate for RouteName
ItemTemplate for StopsOnRoute.
I have a ListView and its ItemsSource.
ListView IList = new ListView();
IList.ItemsSource = _routine_names;
In order to customize the Data Template for each item I'm doing:
IList.ItemTemplate = new DataTemplate(()=>
{
Label Routine_Name = new Label(); // should be_routine_names
return new ViewCell
{
View = new StackLayout{
Children = {Routine_Name}
}
};
});
When I run this my ListView is displayed and the list items are indeed there (they have onclick handlers that work) but there is no text, which should be whatever is in _routine_names.
My question how do I get the Label in the DataTemplate to be items in _routine_names?
The reason I'm adding a DataTemplate is so I can add swipe to delete.
You can just use the built in TextCell for what you're trying to do. It has a bindable TextProperty and an optional DetailProperty if you want a smaller text line below the main one.
IList.ItempTemplate = new DataTemplate(()=>
{
var textCell = new TextCell();
textCell.ContextActions.Add(yourAction);
textCell.SetBinding(TextCell.TextProperty, ".");
return textCell;
}
IList.ItemsSource = _routine_names;
yourAction is of type MenuItem as seen here
Also, please notice that IList is a name of a class in System.Collection namespace so I'd use something else there.
I am using WinForms C#
Is there any way to get following behavior:
bind List to CheckedListBox
When I add elements to list CheckedList box refereshes
When I change CheckedListBox the list changes
I tried to do the following:
Constructor code:
checkedlistBox1.DataSource = a;
checkedlistBox1.DisplayMember = "Name";
checkedlistBox1.ValueMember = "Name";
Field:
List<Binder> a = new List<Binder> { new Binder { Name = "A" } };
On button1 click:
private void butto1_Click(object sender, EventArgs e)
{
a.Add(new Binder{Name = "B"});
checkedListBox1.Invalidate();
checkedListBox1.Update();
}
But the view does not update .
Thank You.
Change this line:
List<Binder> a = new List<Binder> { new Binder { Name = "A" } };
to this:
BindingList<Binder> a = new BindingList<Binder> { new Binder { Name = "A" } };
It will just work without any other changes.
The key is that BindingList<T> implements IBindingList, which will notify the control when the list changes. This allows the CheckedListBox control to update its state. This is two-way data binding.
Also, you could change these two lines:
checkedListBox1.Invalidate();
checkedListBox1.Update();
to this (more readable and essentially does the same thing):
checkedListBox1.Refresh();
Two things you may wish to look at:
Use a BindingList
Add a BindableAttribute to your Name property
Does your List<Bender> need to be some kind of observable collection, like ObservableCollection<Bender> instead?
The proper way of binding a checked listbox is:
List<YourType> data = new List<YourType>();
checkedListBox1.DataSource = new BindingList<YourType>(data);
checkedListBox1.DisplayMember = nameof(YourType.Name);
checkedListBox1.ValueMember = nameof(YourType.ID);
Note to self.
The issue i have every time binding it is that the properties DataSource, DisplayMember and ValueMember are not suggested by intellisense and i get confused.
I am a little bit puzzled as to how I can optimize my program by utlizing DataBindings. My program uses several Linq2SQL bound Objects storing the Data. All ORM objects are stored in a hierarchy. In a second GUI project I am displaying this data in some Text and Combo Box fields.
The data structure hierarchy is as follows:
JobManager contains a Dictionary of Jobs
Each Job contains a Dictionary of Jobitems
Each Jobitem contains exactly one Article
Job, Jobitem and Article each are Linq2SQL objects, representing a ORM.
Now I have a GUI with 2 list views and a tab pane. The tab pane displays the properties of jobs, jobitems and articles and offers the possibility to modify jobs and jobitems. The GUI should behave like this:
When a Job is selected in the first ListView, the related jobitems will be shown in the second ListView and detail information about the job are shown in the tab pane.
When a Jobitem is selected in the second ListView, the jobitem details and article details are shown in the tab pane, but only the Jobitem info is editable.
When changes are done, the user has to intentionally save them. Otherwise the changes should be discarded and not synced to the database.
How can I achieve this behaviour with DataBinding?
Especially, can I bind a complete collection to a single TextField once and shift through its position dictated by the selection in the ListViews? Or do I have to add and remove individual DataBindings on a per Job basis for every selection the user conducts?
Do you really mean "Dictionary"? Winform binding is OK with lists (IList/IListSource), but not with dictionary. Additionally, ListView isn't quite as easy to bind to as some other controls.
Other than that, it should work purely using the mapping names - I'll try to do a simple example...
Edit with basic example from Northwind; note that the data-context should ideally not be long lived; you may also want to look at things like repository implementations rather than direct binding:
using System;
using System.Windows.Forms;
using SomeNamespaceWithMyDataContext;
static class Program
{
[STAThread]
static void Main() {
MyDataContext ctx = new MyDataContext();
BindingSource custs = new BindingSource() {
DataSource = ctx.Customers};
BindingSource orders = new BindingSource {
DataMember = "Orders", DataSource = custs};
Button btn;
using (Form form = new Form
{
Controls = {
new DataGridView() {
DataSource = orders, DataMember = "Order_Details",
Dock = DockStyle.Fill},
new ComboBox() {
DataSource = orders, DisplayMember = "OrderID",
Dock = DockStyle.Top},
new ComboBox() {
DataSource = custs, DisplayMember = "CompanyName",
Dock = DockStyle.Top},
(btn = new Button() {
Text = "Save", Dock = DockStyle.Bottom
}), // **edit here re textbox etc**
new TextBox {
DataBindings = {{"Text", orders, "ShipAddress"}},
Dock = DockStyle.Bottom
},
new Label {
DataBindings = {{"Text", custs, "ContactName"}},
Dock = DockStyle.Top
},
new Label {
DataBindings = {{"Text", orders, "RequiredDate"}},
Dock = DockStyle.Bottom
}
}
})
{
btn.Click += delegate {
form.Text = "Saving...";
ctx.SubmitChanges();
form.Text = "Saved";
};
Application.Run(form);
}
}
}
As an aside - note that the syntax:
DataBindings = {{"Text", orders, "ShipAddress"}}
Is equivalent to:
someTextBox.DataBindings.Add("Text", orders, "ShipAddress");
(I only add this as it is a common question)