Adding WPF content dynamically - c#

What I would like to do is to create a class that contains for instance a Label and Text box. It can differs. I would like to create and add this obj dynamically.
I would like to bind that data to its properties and of-course show the changes immediately:
My steps are:
Create Class e.g. WpfObject
Create new Label
Create new TextBox
Create property
Constructor with or without parameters
Constructor contains databinding settings
I followed WPF tutorial on wpf-tutorial page also checked msdn for hints, and tried to do analogically and not to forgot any step . Of course I tried to google where the problem can be.
By debugging I just found that onPropertyChanged resp. PropertyChanged still returns null.
Well I do not know for what I have to look. I tried to learn something from another issues stated here, or on the web but probably I understand something wrong or forgot something.
So would like to ask for some hint, or help.
Here I am adding my code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
List<infoObject> LinfoObjectList = new List<infoObject>();
private void btnAddBox_Click(object sender, RoutedEventArgs e)
{
infoObject theObject = new infoObject("theinfo");
LinfoObjectList.Add(theObject);
mainGrid.Children.Add(theObject.AddLabel());
mainGrid.Children.Add(theObject.AddTextbox());
}
private void btnCustomBox_Click(object sender, RoutedEventArgs e)
{
foreach(var item in LinfoObjectList)
{
item.ChangeInfoObject();
}
}
}
public class infoObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _sTheText;
string STheText
{
set { _sTheText = value;
OnPropertyChanged("sTheText");
}
get { return _sTheText; }
}
Label theLabel = new Label();
TextBox theTextBox = new TextBox();
int i;
public infoObject(string _objectName){
STheText = " ";
i = 0;
theLabel.Width = 100;
theLabel.Height = 25;
theLabel.Content = _objectName;
theTextBox.Width = 100;
theTextBox.Height = 30;
theTextBox.Text = STheText.ToString();
Binding binding = new Binding();
binding.Path = new PropertyPath("sTheText");
theTextBox.SetBinding(TextBox.TextProperty, binding);
}
public void ChangeInfoObject()
{
STheText = "textWasChanged"+i.ToString();
i = +1;
}
public Label AddLabel()
{
return this.theLabel;
}
public TextBox AddTextbox()
{
return this.theTextBox;
}
public void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
edit:
to clarify what I would like to do:
I would like to make some library or sth. like that and that in future I will be able to create textboxes , labels, buttons for for instance each of element I have stored in list or elsewhere.
So I just will add e.g. Element.add( starting position , left margin ,topmargin, root-name , otherparams) and it will create these mentioned dynamically and I get rid of positioning, putting into grid, for each element.

Related

How to bind a list of classes' property to a series of labels

So, searching did no help and I'm kind of new to binding world.
to simplify it as much as I can:
I have 2 windows and a class. In the first window I declare a list of my class globally: List<MyClass> MyList = new List<MyClass>();
The class supports INotifyPropertyChanged using PropertyChanged.Fody Nuget package.
class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string FirstName { get; set; }
}
In the first window, I have a textbox and a button.
when I press the button, a new MyClass with the FirstName property of TextBox.Text is added to MyList. Then a new row is added to the second window's main grid and a label is added to the new row:
private void Button_Click(object sender, RoutedEventArgs e)
{
MyClass mc = new MyClass() { FirstName = TextBox.Text; };
MyList.Add(mc);
//find my second window and add row and label to its grid
foreach (Window window in Application.Current.Windows)
{
if (window.GetType() == typeof(SecondWindow))
{
Grid mgrid = (window as SecondWindow).MainGrid;
mgrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
Label FN = new Label()
{
Name = "lbl" + mc.FirstName,
Content = mc.FirstName,
};
mgrid.Children.Add(FN);
Grid.SetRow(FN, mgrid.RowDefinitions.Count - 2);
}
}
}
Now, in the code above, I know I should change Content = mc.FirstName somehow to keep it binded to the class property, but have no idea how, and searching didn't exactly help me solve it.
Anyone has an idea what I should do?
You can add a binding in code behind with something like this:
Label label = new Label() {
Name = "lbl" + mc.FirstName
};
Binding binding = new Binding()
{
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Path = new PropertyPath("FirstName")
};
label.SetBinding(ContentProperty, binding);

How do you create an OnClick command in WPF MVVM with a programmatically created button?

I am writing a WPF application that programmatically creates a few buttons. How do you create an OnClick command for a button in the ViewModel? I would like to add a command to clear all TextBoxes with the ResetButton.
new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
new Button { Name = "SendButton", Content = "Send", MinWidth = 50, MaxHeight = 30, Margin = new Thickness(5), Background = Brushes.DodgerBlue },
new Button { Name = "ResetButton", Content = "Reset", MinWidth = 50, MaxHeight = 30, Margin = new Thickness(5), Background = Brushes.DarkRed}
}
});
Do you have access to the view model as you are creating the Stack Panel?
If so, you have your View Model expose a Command:
var myViewModel = (MyViewModel)this.DataContext;
Button sendButton = new Button
{
Name = "SendButton",
Command = myViewModel.SendCommand,
// etcd
}
And in your view model:
class MyViewModel : INotifyPropertyChanged
{
private class SendCommand : ICommand
{
private readonly MyViewModel _viewModel;
public SendCommand(MyViewModel viewModel)
{
this._viewModel = viewModel;
}
void ICommand.Execute(object parameter)
{
_viewModel.Send();
}
bool ICommand.CanExecute(object p)
{
// Could ask the view nodel if it is able to execute
// the command at this moment
return true;
}
}
public ICommand SendCommand
{
get
{
return new SendCommand(this);
}
}
internal void Send()
{
// Invoked by your command class
}
}
This example creates a new class just for this one command. After you've done this more than once you'll probably see a pattern, and wrap it up in a generic utility class. See http://www.wpftutorial.net/delegatecommand.html for an example, or use any of the myriad of WPF extension libraries.
Answer to your first question:
How do you create an OnClick command for a button in the ViewModel?
You can actually do this to add onclick for a button:
Button button = new Button { Name = "ResetButton"};
button.Click += button_Click; (button_Click is the name of method)
void button_Click(object sender, RoutedEventArgs e)
{
//do what you want to do when the button is pressed
}
by the way, Andrew's solution is better. ooaps.

WinForms DataBinding with DataGridView

I though I would post this as after spending several hours trying to work it out I am getting nowhere. Firstly, I am fully aware that databinding in WinForms is not the best. That said it does work in most scenarios.
In my scenario, I have a binding source which is the master for my form. The object that is used for this binding source has a few simple properties and two binding lists as properties as well. Both this class, and the class type for the binding lists implement INotifyPropertyChanged. On my form, I have two DataGridViews for displaying the contents of the binding list properties.
This is also done through databinding at design time. I have two binding sources for each which use the main binding source as there data source and then the respective bindinglist properties as the data member.
So far, I would consider this to be fairly standard.
To update what is in these lists I have buttons to show a form that creates a new item, which I then add to the lists using BindingList.Add().
Now in code, if you debug, the items are in the lists, however, the grids are not updating.
But if I add a listbox to the form which uses just one of the list binding sources then both of the grids start refreshing as expected.
I apologise if any of this is unclear, I have tried to explain as best as I can with a confusing situation.
Any thoughts would be helpful as I really don't want to have to use a hidden list box.
This code works fine for me
BindingList<Foo> source; // = ...
private void Form1_Load(object sender, EventArgs e)
{
this.dataGridView1.DataSource = new BindingSource { DataSource = source };
this.dataGridView2.DataSource = new BindingSource { DataSource = source, DataMember = "Children" };
}
private void button1_Click(object sender, EventArgs e)
{
source.Add(new Foo { X = Guid.NewGuid().ToString() });
}
private void button2_Click(object sender, EventArgs e)
{
source[0].Children.Add(new FooChild { Y = Guid.NewGuid().ToString() });
}
with the model
public class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
string x;
public string X
{
get { return x; }
set
{
x = value;
this.NotifyPropertyChanged();
}
}
BindingList<FooChild> children;
public BindingList<FooChild> Children
{
get { return children; }
set
{
children = value;
this.NotifyPropertyChanged();
}
}
}
public class FooChild : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
string y;
public string Y
{
get { return y; }
set
{
y = value;
this.NotifyPropertyChanged();
}
}
}
Both grids get refreshed.
I hope this helps you
Edit
I changed the Form1_Load impl

WinForms: ListBox item updates case insensitive?

I have a Windows Forms ListBox data-bound to a BindingList of business objects. The ListBox's displayed property is a string representing the name of the business object. I have a TextBox that is not data-bound to the name property but instead is populated when the ListBox's selected index changes, and the TextBox, upon validation, sets the business object's name property and then uses BindingList.ResetItem to notify the BindingList's bound control (the ListBox) to update itself when the TextBox's text value is changed by the user.
This works great unless the name change is only a change in case (i.e. "name" to "Name"), in which case the ListBox doesn't get updated (it still says "name", even though the value of the underlying business object's name property is "Name").
Can anyone explain why this is happening and what I should do instead? My current workaround is to use BindingList.ResetBindings, which could work for me but may not be acceptable for larger datasets.
Update 9/27/2011: Added a simple code example that reproduces the issue for me. This is using INotifyPropertyChanged and binding the textbox to the binding list. Based on How do I make a ListBox refresh its item text?
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WinformsDataBindingListBoxTextBoxTest
{
public partial class Form1 : Form
{
private BindingList<Employee> _employees;
private ListBox lstEmployees;
private TextBox txtId;
private TextBox txtName;
private Button btnRemove;
public Form1()
{
InitializeComponent();
FlowLayoutPanel layout = new FlowLayoutPanel();
layout.Dock = DockStyle.Fill;
Controls.Add(layout);
lstEmployees = new ListBox();
layout.Controls.Add(lstEmployees);
txtId = new TextBox();
layout.Controls.Add(txtId);
txtName = new TextBox();
layout.Controls.Add(txtName);
btnRemove = new Button();
btnRemove.Click += btnRemove_Click;
btnRemove.Text = "Remove";
layout.Controls.Add(btnRemove);
Load += new EventHandler(Form1_Load);
}
private void Form1_Load(object sender, EventArgs e)
{
_employees = new BindingList<Employee>();
for (int i = 0; i < 10; i++)
{
_employees.Add(new Employee() { Id = i, Name = "Employee " + i.ToString() });
}
lstEmployees.DisplayMember = "Name";
lstEmployees.DataSource = _employees;
txtId.DataBindings.Add("Text", _employees, "Id");
txtName.DataBindings.Add("Text", _employees, "Name");
}
private void btnRemove_Click(object sender, EventArgs e)
{
Employee selectedEmployee = (Employee)lstEmployees.SelectedItem;
if (selectedEmployee != null)
{
_employees.Remove(selectedEmployee);
}
}
}
public class Employee : INotifyPropertyChanged
{
private string name;
private int id;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
public int Id
{
get { return id; }
set
{
id = value;
OnPropertyChanged("Id");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
}
Update 9/28/2011: The problem seems to be internal to the ListBox control, specifically the way it decides (not) to update an item if its string representation is equivalent to the new value, ignoring case differences. As far as I can tell this is hard coded into the control with no way to override it.
Think I found the problem:
I just hope this concept will be helpfull to solve your problem
I have a TextBox, the user input is compared to string "Name". My button click event is:
private void btn_Click(object sender, EventArgs e)
{
if(txt.Text == "Name")
MessageBox.Show("Value is Same");
}
If you write "name" in textbox, condition will be false. If you type type "Name" in textbox, condition will be true.
Now try changing the btn click:
using System.Globalization;
private void btn_Click(object sender, EventArgs e)
{
TextInfo ChangeCase = new CultureInfo("en-US", false).TextInfo;
string newText = ChangeCase.ToTitleCase(txt.Text);
if (newText == "Name")
MessageBox.Show("Value is Same");
}
now you type "name" or "Name" condition is true.
Remember it will just capaitalize the first letter of the string suplied. "my name" will outputted as "My Name".
And if your condition says:
if(txt.Text == "name")
MessageBox.Show(Value is Same);
Then you can try something like
string newText = (txt.Text).ToLower();
if(newText == "name")
MessageBox.Show(Value is Same);
Here the supplied string will outputted in the lower case always.
Hope it helps.
This really is the same problem as when renaming files or directories while only case is different. I suggest the same work-around that I found earlier:
if (oldValue.ToUpper() == newValue.ToUpper()){
ListBox1.Items[i] = newValue + "_tmp"; // only adding stuff to force an update
ListBox1.Items[i] = newValue; // now the new value is displayed, even only case has changed
}
Now for your question, I suggest you try to check if the setter is changing a value only in lower/upper case (a.ToUpper() == b.ToUpper()). If true, then first give a extra change, before the intended change, something like:
name = value + "_tmp";
OnPropertyChanged("Name");
name = value;
OnPropertyChanged("Name");
Hope this helps.

.NET WinForms INotifyPropertyChanged updates all bindings when one is changed. Better way?

In a windows forms application, a property change that triggers INotifyPropertyChanged, will result in the form reading EVERY property from my bound object, not just the property changed. (See example code below)
This seems absurdly wasteful since the interface requires the name of the changing property. It is causing a lot of clocking in my app because some of the property getters require calculations to be performed.
I'll likely need to implement some sort of logic in my getters to discard the unnecessary reads if there is no better way to do this.
Am I missing something? Is there a better way? Don't say to use a different presentation technology please -- I am doing this on Windows Mobile (although the behavior happens on the full framework as well).
Here's some toy code to demonstrate the problem. Clicking the button will result in BOTH textboxes being populated even though one property has changed.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace Example
{
public class ExView : Form
{
private Presenter _presenter = new Presenter();
public ExView()
{
this.MinimizeBox = false;
TextBox txt1 = new TextBox();
txt1.Parent = this;
txt1.Location = new Point(1, 1);
txt1.Width = this.ClientSize.Width - 10;
txt1.DataBindings.Add("Text", _presenter, "SomeText1");
TextBox txt2 = new TextBox();
txt2.Parent = this;
txt2.Location = new Point(1, 40);
txt2.Width = this.ClientSize.Width - 10;
txt2.DataBindings.Add("Text", _presenter, "SomeText2");
Button but = new Button();
but.Parent = this;
but.Location = new Point(1, 80);
but.Click +=new EventHandler(but_Click);
}
void but_Click(object sender, EventArgs e)
{
_presenter.SomeText1 = "some text 1";
}
}
public class Presenter : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _SomeText1 = string.Empty;
public string SomeText1
{
get
{
return _SomeText1;
}
set
{
_SomeText1 = value;
_SomeText2 = value; // <-- To demonstrate that both properties are read
OnPropertyChanged("SomeText1");
}
}
private string _SomeText2 = string.Empty;
public string SomeText2
{
get
{
return _SomeText2;
}
set
{
_SomeText2 = value;
OnPropertyChanged("SomeText2");
}
}
private void OnPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
}
The reason why all properties are being read when the event gets fired rests in the PushData method called on the binding object when the ProperyChanged event is fired. If you look at the stacktrace, you will notice that the PropValueChanged method of the internal object BindToObject is called, that in turn calls the Oncurrentchanged event on the BindingManager. The binding mechanism keeps track of the current item changes, but it doesn't do a more granular distinction. The "culprit" PushData method calls the getter on your properties (take a look at the code using reflector). So there is no way around it. That being said, as a rule of thumb, in the get and set accessors it is not recommended to do heavy processing, use separate get and set methods for that (if possible)
Also take a look at this article, and this comment in particular (http://www.codeproject.com/Messages/2514032/How-Binding-watches-control-properties-i-e-how-doe.aspx), that explains exactly how the propertychanged event gets fired, though it will not address your getter problem: http://www.codeproject.com/KB/database/databinding_tutorial.aspx?msg=2514032
An idea to explore is to delay the getter being called. You can achieve this by playing around with the ControlUpdateMode property of the binding. When this value is set to Never, the corresponding control will not update when there is a change. However, when you switch the value back to OnPropertyChanged, PushData method will be called, so the getters will be accessed. So considering your example this code will temporary prevent the textbox 2 to update:
void but_Click(object sender, EventArgs e)
{
txt2.DataBindings[0].ControlUpdateMode = ControlUpdateMode.Never;
_presenter.SomeText1 = "some text 1";
}
I'm testing subclassing binding like this and managing OnPropertyChanged, maybe helps you.
public class apBinding : Binding
{
public apBinding(string propertyName, INotifyPropertyChanged dataSource, string dataMember)
: base(propertyName, dataSource, dataMember)
{
this.ControlUpdateMode = ControlUpdateMode.Never;
dataSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.BindingMemberInfo.BindingField)
{
this.ReadValue();
}
}
}
Now the problem that i find is that the control overwrites the value of the linked object,
so i modified to
public class apBinding : Binding
{
public apBinding(string propertyName, INotifyPropertyChanged dataSource, string dataMember)
: base(propertyName, dataSource, dataMember)
{
dataSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.ControlUpdateMode = ControlUpdateMode.Never;
if (e.PropertyName == this.BindingMemberInfo.BindingField)
{
this.ReadValue();
}
}
}
then the first time propertychanges is called i disable controlupdate. and the control is correctly updated at the first run.

Categories