Accessing TextBox.Text on dynamically added UserControl - c#

On load of a new Window I add a dynamic number of UserControls (SetInformation) to my window as below
public Window_NewWorkoutLine(int NoOfSets, int workoutLineId)
{
InitializeComponent();
currentWorkoutLineId = workoutLineId;
for (int x = 1; x <= NoOfSets; x++)
{
SetInformation setInformation = new SetInformation(x);
StackPanel_Main.Children.Add(setInformation);
}
}
Each of the UserControls contains 2 textboxes, what I need to do is grab the Text property from each textbox on each UserControl and use them in an insert query to a database. The data from each UserControl will be added to a seperate row in the database.
Any ideas guys?
Thanks in advance

I think you could enumerate all UIElement or "StackPanel_Main", and then cast them.
For example (with use of System.Linq) :
foreach (SetInformation setInformation in StackPanel_Main.Children.OfType<SetInformation>())
{
string txt1 = setInformation.Text1;
string txt2 = setInformation.Text2;
}
But I think this would be better to associate a "Value Object" to each instance of your UserControl to separate User Interface (UI) from Data. You could do that with Binding for example.
First declare your class which will contain your data with implementation of INotifyPropertyChanged for UI integration :
/// <summary>
/// This objet contains all useful data
/// </summary>
public class InformationValueObject : INotifyPropertyChanged
{
private string _text1;
private string _text2;
public string Text1
{
get { return _text1; }
set { _text1 = value; OnPropertyChanged("Text1"); }
}
public string Text2
{
get { return _text2; }
set { _text2 = value; OnPropertyChanged("Text2"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Then add Binding to your UserControl, as you like, for example you could just set the DataContext of your UserControl like bellow, and then Bind each property to each TextBox from DataContext :
public partial class Window_NewWorkoutLine : Window
{
private List<InformationValueObject> _valueObjects = new List<InformationValueObject>();
public Window_NewWorkoutLine(int NoOfSets, int workoutLineId)
{
InitializeComponent();
currentWorkoutLineId = workoutLineId;
for (int x = 1; x <= NoOfSets; x++)
{
SetInformation setInformation = new SetInformation(x);
InformationValueObject vo = new InformationValueObject();
setInformation.DataContext = _vo;
_valueObjects.Add(vo);
StackPanel_Main.Children.Add(setInformation);
}
}
}
It is then easy to retrieve values by reading private collection "_valueObjects". No more need to enumerate User Interface composition.
It is really important to separate data and display.
My example could also be improved a lot.
For example, a ListView is composed (by default) by a StackPanel inside a ScrollViewer, and you can set the "ItemsSource" property with _valueObjects collection, and customize Item template to use your custom UserControl.
Then you can improve again by using a MVVM model with the Binding of your collection onto your ListBox. You could also use an ObservableCollection<InformationValueObject> instead of a List<InformationValueObject> if you want to be able to add or remove items dynamically...
I know this is not possible to masterize everything, but I think it could be great to tend to these solutions by separating data and visualization.
Best regards,

Maybe try adding a public property to your control to get and set text to the text boxes:
public string Text
{
get
{
return txtTextBox.Text.ToString();
}
set
{
txtTextBox.Text = HttpUtility.HtmlDecode(value);
}
}

You can add accessors to the user controls:
public class SetInformation{
private TextBox box1;
private TextBox box2;
public string Box1Text{
get{
return box1.Text;
}
}
public string Box2Text{
get{
return box2.Text;
}
}
...
...
}

Related

INotifyPropertyChanged in nested object

I have 2 main classes. The first class represents a Cell that can have values X, O or Empty. I have implemented INotifyPropertyChanged on this.
public class Cell : INotifyPropertyChanged
{
private Symbol state;
public Symbol State
{
get { return state; }
set
{
if (value == Symbol.X || value == Symbol.O)
state = value;
OnPropertyChanged("State");
}
}
public Cell()
{
state = Symbol.Empty;
}
public enum Symbol
{
X, O, Empty
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
The second class contains an object of this class and is also set as the datacontext for my main window.
public class Board
{
private Cell testCell;
public Cell TestCell
{
get { return testCell; }
set { testCell = value; }
}
public Board()
{
TestCell = new Cell();
}
public void Cell_Click(int cellNum)
{
TestCell.State = Cell.Symbol.O;
}
}
In my mainwindow I have set the datacontext as board, and also contains a button_click function.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Board();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Board board = this.DataContext as Board;
board.Cell_Click(cellNum);
}
}
In my XAML I have bound to Cell object within board using a button style like this:
<Setter Property="Background" Value="{Binding TestCell,
Converter={StaticResource BGConverter}}"/>
BGConverter is a custom converter that accepts a Cell object and converts it into a Colors object. I believe I am indeed directly binding to an object that has INotify implemented, so there's no issue of nested objects. However the binding doesn't reflect changes when I click. When debugging, I found that PropertyChanged event is always null.
The closest answer I found for this is that the event will be subscribed to only if the class Cell is my datacontext. Atleast that's what I understood. How can I correct this problem?
Also I am fresh out of college, currently learning WPF on a new job, so any general recommendations are welcome too.
Thanks
Simply bind to TestCell.State instead of TestCell
I'm pretty new at this myself, but I believe your data context needs to implement INotifyPropertyChanged as well.
That is, your Board class needs to listen to the PropertyChanged event of the cells and fire its own PropertyChanged event when this happens.

notifying bindingSource.position. Using 2 way binding in winforms to select the current record

I have a bindingSource in winforms as well as a controller class.
I want to be able to set the selected record from within the controller class using 2 way binding.
That is If the form is displaying and I set the SelectedPerson in the controller then the bindingSOurce should make that person the current record.
My controller code is
public class PeopleController : BaseController
{
private SortableBindingList<Person> _blvPersons;
public SortableBindingList<Person> BlvPersons
{
get
{
return this._blvPersons;
}
set
{
this._blvPersons = value;
this.SendChange("BlvPersons");
}
}
private Person _selectedPerson;
public Person SelectedPerson
{
get
{
return this._selectedPerson;
}
set
{
this._selectedPerson = value;
this.SendChange("SelectedPerson");
this.SendChange("BlvPersons");
this.Trace("## SelectedPerson = {0}", value);
}
}
public void InitBindingList
{
using (var repo = new PeopleRepository(new OrganisationContext()))
{
IList<Person> lst = repo.GetList(p => p.Id > 0 && p.Archived == false, x => x.Organisation);
this.BlvPersons = new SortableBindingList<Person>(lst);
} }
}
//ect
}
public class BaseController : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
public void SendChange(string propertyName)
{
System.Diagnostics.Debug.WriteLine("PropertyChanged {0} = {1}", propertyName, GetType().GetProperty(propertyName).GetValue(this, null));
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// etc
I have a bindingSource on my form and set bindingSource.DataSource = controller.BlvPersons
If I Update data values using the controller I will see these changes in the form.
However I cant work out how to set the current record in the controller and see the change in the form.
You can use BindingSource.Find method and set the Position property to the results of the Find method.
The Find method can only be used when the underlying list is an
IBindingList with searching implemented. This method simply refers the
request to the underlying list's IBindingList.Find method.
To implement search on a generic BindingList requires various steps. First, you have to indicate that searching is supported by overriding the SupportsSearchingCore property. Next, you have to implement the IBindingList.Find method, which performs the search.
You can use examples from here or here.
Because I don't want a winforms reference in my controller class, I don't want to share the bindingSource between the form and the controller.
Instead I came up with the idea of having a RecordPosition property in the controller and binding it to a textbox
In my form I have
BindHelper.BindText(this.textRecordPosition,this.controller,"RecordPosition");
private void textRecordPosition_TextChanged(object sender, EventArgs e)
{
this.bindingSource.Position = Convert.ToInt32(textRecordPosition.Text) -1;
}
private void bindingSource_PositionChanged(object sender, EventArgs e)
{
this.controller.RecordPosition = this.bindingSource.Position + 1;
}
In my controller I have
public int RecordPosition
{
get
{
return this._position;
}
set
{
this._position = value;
this.SendChange("RecordPosition");
}
}
In my BindHelper class I have
public static void BindText(TextBox box, object dataSource, string dataMember)
{
var bind = new Binding("Text", dataSource, dataMember, true, DataSourceUpdateMode.OnPropertyChanged);
box.DataBindings.Add(bind);
}

How to bind a combo-box to a collection of multi-language values in WPF?

I am trying to set up a multi-language application, so when the user changes the display language all the texts in all the open windows change automatically.
I am having issues through with binding combo-box control. The binding needs to be done in code-behind as I have dynamic content coming from a database, and sometimes I even have to create additional combo-boxes at runtime.
Also I do not want to keep the translations in the database because I do not want to query the database every time a user is changing the display language.
What I did until now:
in xaml:
<ComboBox x:Name="cmb"/>
and in C#:
public class MyCmbItem
{
public int Index { get; set; }
public string Text { get; set; }
}
private ObservableCollection<MyCmbItem> LoadText()
{
ObservableCollection<MyCmbItem> _result = new ObservableCollection<MyCmbItem>();
foreach (var _item in _list)
{
//the list is coming from a database read
_result.Add(new MyCmbItem { Index = _item.Value, Text = _res_man_global.GetString(_item.KeyText, _culture) });
}
return _result;
}
public ObservableCollection<MyCmbItem> MyTexts
{
get { return LoadText(); }
set {} //I do not have to add/remove items at runtime so for now I leave this empty
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
...
LoadList(); //this adds values in _list
cmb.ItemsSource = MyTexts; //this populates the combo-box
Here I got stuck and I do not know how to determine the combo-box to refresh the displayed texts. The method must achieve that if I have several windows opened each containing a random number of combo-boxes, when I change the current language all the combo-boxes in all the windows will refresh the displayed list, without affecting other values inside (like the selected item). Does anybody know how this can be done?
Many thanks.
For your xaml UI, the INotifyPropertyChanged interface indicates updates of the viewmodel. You can extend your class like this:
public class MyCmbItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string APropertyName)
{
var property_changed = PropertyChanged;
if (property_changed != null)
{
property_changed(this, new PropertyChangedEventArgs(APropertyName));
}
}
private string _Text;
private string _KeyText;
public int Index { get; set; }
public string Text
{
get { return _Text;}
set {
if (_Text != value)
{
_Text = value;
NotifyPropertyChanged("Text");
}
}
}
public MyCmbItem(string key_text, int index)
{
Index = index;
_KeyText = key_text;
RefreshText();
_res_man_global.LanguageChanged += () => RefreshText();
}
public void RefreshText()
{
Text = _res_man_global.GetString(_KeyText, _culture);
}
}
Your view can simply bind to the Text-property as following:
<DataTemplate DataType="{x:Type local:MyCmbItem}">
<TextBlock Text="{Binding Path=Text}"/>
</DataTemplate>
Note: I assumed that your language class is global and has some kind of language-changed notification event.

DataGrid calculated columns

I am trying to transfer my excel app to WPF datagrid. I am going to enter data to Column A and in column B I would like to make calculation taking previus cell and current cell of A column and add Column B previus cell.
calculation example : B2 = B1 + (A2-A1). What is best approach of doing so?
Personally, I'd start by creating a class that represents the records and implement INotifyPropertyChanged on that class.
public class recordObject : INotifyPropertyChanged
{
private int a;
public int A
{
get
{
return a;
}
set
{
a = value;
OnPropertyChanged("A");
}
}
private int b;
public int B
{
get
{
return b;
}
set
{
b = value;
OnPropertyChanged("B");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then, in your code behind on the window you're showing the datagrid, you'll want to subscribe to PropertyChanged on each object in the list. Then you'd have to manually compute the column values whenever those properties changed. Ick, I know, but it'd work.
The property changed event would look like:
void recordObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var objectList = DataGrid.ItemsSource as List<recordObject>;
var myRecord = sender as recordObject;
if (objectList != null && myRecord != null)
{
int idx = objectList.IndexOf(myRecord);
// Perform your calculations here using idx to access records before and after the current record
// making sure to check for list boundaries for top and bottom.
// Also note that this will likely kick off cascading event calls so make sure you're only changing
// the previous or following record object.
}
}
If you hook this event onto all the records in your bound list, then it'll fire whenever any property is changed. In the class above, that'd apply to both A and B. You can filter which properties you're interested in monitoring through e.PropertyName (a simple string comparison) and guage the business logic accordingly. If you want to maintain encapsulation, or at least, put the business logic for the object on the object itself, this method could be a static one on the class recordObject. You'd have to provide for getting hold of the datagrid from that static method, though (likely through a static property on your window). So:
public static void recordObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
and connected so:
record.PropertyChanged += new PropertyChangedEventHandler(recordObject.recordObject_PropertyChanged);
The best thing is to implement that logic in a class, and bind the grid to the respective properties. For instance:
class SomeData
{
int A { get; set; }
int B { get; set; }
int AminusB { get { return A - B; } }
}

C# custom listbox GUI

I have a list of classes, but different children have different properties that need to be displayed.
What I want to achieve is to have a listbox-type control in the gui which enables each child to display it's properties the way it wants to - so not using the same pre-defined columns for every class.
I envisage something like the transmission interface (below), where each class can paint it's own entry, showing some text, progress bar if relevant, etc.
How can this be achieved in C#?
Thanks for any help.
Let your list items implement an interface that provides everything needed for the display:
public interface IDisplayItem
{
event System.ComponentModel.ProgressChangedEventHandler ProgressChanged;
string Subject { get; }
string Description { get; }
// Provide everything you need for the display here
}
The transmission objects should not display themselves. You should not mix domain logic (business logic) and display logic.
Customized ListBox:
In order to do display listbox items your own way, you will have to derive your own listbox control from System.Windows.Forms.ListBox. Set the DrawMode property of your listbox to DrawMode.OwnerDrawFixed or DrawMode.OwnerDrawVariable (if the items are not of the same size) in the constructor. If you use OwnerDrawVariable then you will have to override OnMeasureItem as well, in order to tell the listbox the size of each item.
public class TransmissionListBox : ListBox
{
public TransmissionListBox()
{
this.DrawMode = DrawMode.OwnerDrawFixed;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
e.DrawBackground();
if (e.Index >= 0 && e.Index < Items.Count) {
var displayItem = Items[e.Index] as IDisplayItem;
TextRenderer.DrawText(e.Graphics, displayItem.Subject, e.Font, ...);
e.Graphics.DrawIcon(...);
// and so on
}
e.DrawFocusRectangle();
}
}
You can let your original transmission class implement IDisplayItem or create a special class for this purpose. You can also have different types of objects in the list, as long as they implement the interface. The point is, that the display logic itself is in the control, the transmission class (or whatever class) only provides the information required.
Example:
Because of the ongoing discussion with Mark, I have decided to include a full example here. Let's define a model class:
public class Address : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (_Name != value) {
_Name = value;
OnPropertyChanged("Name");
}
}
}
private string _City;
public string City
{
get { return _City; }
set
{
if (_City != value) {
_City = value;
OnPropertyChanged("City");
OnPropertyChanged("CityZip");
}
}
}
private int? _Zip;
public int? Zip
{
get { return _Zip; }
set
{
if (_Zip != value) {
_Zip = value;
OnPropertyChanged("Zip");
OnPropertyChanged("CityZip");
}
}
}
public string CityZip { get { return Zip.ToString() + " " + City; } }
public override string ToString()
{
return Name + "," + CityZip;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Here is a custom ListBox:
public class AddressListBox : ListBox
{
public AddressListBox()
{
DrawMode = DrawMode.OwnerDrawFixed;
ItemHeight = 18;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
const TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
if (e.Index >= 0) {
e.DrawBackground();
e.Graphics.DrawRectangle(Pens.Red, 2, e.Bounds.Y + 2, 14, 14); // Simulate an icon.
var textRect = e.Bounds;
textRect.X += 20;
textRect.Width -= 20;
string itemText = DesignMode ? "AddressListBox" : Items[e.Index].ToString();
TextRenderer.DrawText(e.Graphics, itemText, e.Font, textRect, e.ForeColor, flags);
e.DrawFocusRectangle();
}
}
}
On a form, we place this AddressListBox and a button. In the form, we place some initializing code and some button code, which changes our addresses. We do this in order to see, if our listbox is updated automatically:
public partial class frmAddress : Form
{
BindingList<Address> _addressBindingList;
public frmAddress()
{
InitializeComponent();
_addressBindingList = new BindingList<Address>();
_addressBindingList.Add(new Address { Name = "Müller" });
_addressBindingList.Add(new Address { Name = "Aebi" });
lstAddress.DataSource = _addressBindingList;
}
private void btnChangeCity_Click(object sender, EventArgs e)
{
_addressBindingList[0].City = "Zürich";
_addressBindingList[1].City = "Burgdorf";
}
}
When the button is clicked, the items in the AddressListBox are updated automatically. Note that only the DataSource of the listbox is defined. The DataMember and ValueMember remain empty.
yes, if you use WPF it is quite easy to do this. All you have to do is make a different DataTemplate for your different types.
MSDN for data templates
Dr. WPF for Items Control & Data Templates

Categories

Resources