Moving data between two user controls in WinForm Application - c#

As a course project i'm building a form in c# which contains two user controls.
The first user control has a checkedlistbox and the second control has also a checkedlistbox when the first control checkedlistbox will contain list of people (male/female) and the second user control the checkedlistbox will have two options: male, female and when I click a button on the first control which says: "update friends" it's suppose to go to the second control and check if we selected male or female and according to that to update the checkedlistbox in the first user control with friends by gender type by what was selected on the second control.
Basically I want to raise an event every time the button on the first control selected then to get the data from the second control to the first control.
Is it possible to do so between two controls who are inside a form and are different controls?
Any help will be appriciated.
Thanks.

To do this "correctly," you would want to use something like the MVC architecture. It's definitely a lot more work initially to understand and implement but is very useful to know if you plan on doing any serious UI application development. Even if you don't go all the way with it, the concepts are useful to help design even "quick and dirty" applications.
Define your data model without thinking in terms of the UI, e.g.:
internal enum Gender
{
Male,
Female
}
internal class Person
{
public Gender Gender { get; set; }
public string Name { get; set; }
}
// . . .
// Populate the list of people
List<Person> allPeople = new List<Person>();
allPeople.Add(new Person() { Gender = Gender.Male, Name = "Xxx Yyy" });
allPeople.Add(new Person() { Gender = Gender.Female, Name = "Www Zzz" });
// . . .
For the view portion, you would typically use data binding on the UI controls so that the controls will automically reflect changes to the underlying data. However, this can get difficult especially if you are not using a database-like model (e.g. System.Data.DataSet). You may opt to "manually" update the data in the controls which might be fine in a small app.
The controller is the portion that uses the UI events and makes changes to the model, which may then be reflected as changes in the view.
internal class Controller
{
private Gender selectedGender;
private List<Person> allPeople;
private List<Person> friends;
public Controller(IEnumerable<Person> allPeople)
{
this.allPeople = new List<Person>(allPeople);
this.friends = new List<Person>();
}
public void BindData(/* control here */)
{
// Code would go here to set up the data binding between
// the friends list and the list box control
}
// Event subscriber for CheckedListBox.SelectedIndexChanged
public void OnGenderSelected(object sender, EventArgs e)
{
CheckedListBox listBox = (CheckedListBox)sender;
this.selectedGender = /* get selected gender from list box here */;
}
// Event subscriber for Button.Click
public void OnUpdateFriends(object sender, EventArgs e)
{
this.friends.AddRange(
from p in this.allPeople
where p.Gender == this.selectedGender
select p);
// If you use data binding, you would need to ensure a
// data update event is raised to inform the control
// that it needs to update its view.
}
}
// . . .
// On initialization, you'll need to set up the event handlers, etc.
updateFriendsButton.Click += controller.OnUpdateFriends;
genderCheckedListBox.SelectedIndexChanged += controller.OnGenderSelected;
controller.BindData(friendsListBox);
// . . .
Basically, I recommend not having controls talk directly, but rather through a controller-like class as above which has knowledge of the data model and the other controls in the view.

Of course it's possible: you need to make the link between the 2 controls in the form.
Just declare an event 'ButtonClicked' in control #1
Then make a public method 'PerformsClick' on the control #2
And in the form, in the constructor, after the call to InitializeComponent, link the event from the control #1 to the method to the control #2:
control1.ButtonClicked += delegate(sender, e) {
control2.PerformsClick();
};
(I type on the fly to give you an idea, it'll surely not compile)
If you want to pass any data, just add parameters in the PerformsClick method.

Related

How to fill multiple textboxes based on selected row in listbox

I have written a method that populates multiple rows in a listbox (but only displays the Registration number). These rows are called registrationNumber, hireCost, carMake, carModel, and carYear. As it only displays the registration number, I want to populate some textboxes when I select the registration number in the listbox.
I have made a general method of how I believe it will function, but am not sure how to implement it.
Code for inserting data
public void UpdateListBox(string registrationNumber, string hireCost, string carMake, string carModel, string carYear) {
foreach (string item in listBoxVehicles.Items)
{
if (item.Contains(registrationNumber))
{
MessageBox.Show("Registration Number: " + registrationNumber + " already exists in the list! Please try another!");
return;
}
}
listBoxVehicles.Items.Add(registrationNumber);
}
Code for selecting row
private void PopulateTextBoxes() {
if (listBoxVehicles.SelectedIndex != -1)
{
textBoxHireCost.Text = "Selected index hireCost";
textBoxMake.Text = "Selected index carMake";
textBoxModel.Text = "Selected index carModel";
textBoxYear.Text = "Selected index carYear";
}
}
Here is how it might look when I click on a registration number
as the attached image below. The fields on the right have been populated.
How might I populate the values of the selected registration number into the textboxes?
Edit
Here is the method that gets the data for the listbox from another form:
private void StoreData()
{
//Store Data
HomeForm list = (HomeForm)Application.OpenForms["HomeForm"];
list.UpdateListBox(textBoxRegistrationNumber.Text, textBoxHireCost.Text + " ", textBoxMake.Text + " ", textBoxModel.Text + " ", textBoxYear.Text);
this.Hide();
}
In modern programming, there is a tendency to separate your data (= model) from the way that the data is displayed (= view). If you separate them, you will be able to change the way you display the data without having to change your model.
Similarly you can change the model, without having to change the view. For instance, your current model fetches the data from a database, but you won't have to change anything on your view if you decide to read the data from a JSON-file.
Separation of model and view also enables unit testing your model without forms.
Separation of model and view needs something to glue them together. This "adapter" is often called the viewmodel. The abbreviation of these items is often called MVVM.
So let's separate!
Apparently your model has the notion of cars that can be hired. So we need a class:
class Car
{
public string RegistrationNumber {get; set;}
public decimal HireCost {get; set;}
public int ManufacturingYear {get; set;}
... // etc.
}
And of course we need a procedure to fetch the cars that must be displayed:
public IEnumerable<Car> FetchCars(...)
{
// TODO implement; out of scope of the question
}
Using the visual designer you have added the ListBox. This ListBox should display Cars, and for every Car it should display only the registration number.
You can do this using the visual studio designer, another method would be to do it in the constructor:
public MyForm()
{
InitializeComponent();
// From ever Car in the ListBox display the value of property RegistrationNumber
listBox1.Displayember = nameof(Car.RegistrationNumber);
}
To Display the cars:
private ICollection<Car> DisplayedCars
{
get => (ICollection<Car>)this.listBox1.DataSource;
set => this.listBox1.DataSource = value;
}
public void DisplayCars()
{
var carsToDisplay = this.FetchCars();
this.DisplayedCars = carsToDisplay.ToList();
}
And Bingo: all the registration numbers of the cars are displayed.
This display is read-only. Changes that the operator makes: Add / Remove rows, change registration numbers, are not reflected.
This might not be a problem in your current application, but if later you decide to show your cars in a DataGridView, or if you want to use this method in a ListBox that the operator can edit, you might want automatic updating of changes that the operator makes. In that case you should use a BindingList:
private BindingList<Car> DisplayedCars
{
get => (BindingList<Car>)this.listBox1.DataSource;
set => this.listBox1.DataSource = value;
}
To get the selected Car:
private Car SelectedCar => (Car)this.listBox1.SelectedItem;
Or if you allow multiselect:
private IEnumerable<Car> SelectedCars = this.listBox1.SelectedItems.Cast<Car>();
Back to your question
I want to populate some textboxes when I select the registration number in the listbox.
So if the operator selects a Registration number in the list box, you want to display several values of Car properties of the selected Car.
Using visual Studio Designer, or in the constructor:
this.listBox1.SelectedIndexChanged += OnCarSelected;
private void OnCarselected(object sender, ...)
{
Car selectedCar = this.SelectedCar;
this.DisplayCarProperties(selectedCar);
}
private void DisplayCarProperties(Car car)
{
this.textBoxHireCost.Text = car.HireCost.ToString(...);
this.textBoxYear.Text = car.ManufacturingYear.ToString();
...
}
Conclusion
By separating your data from the way that you view your data, your code is much easier to read. The methods are usually one or two lines code. Methods are highly reusable and both the model and the view are easy to change. It is possible to unit test the model without the view.

Update Multiple DataGrids in WPF for Header Title

I have a form that has a dynamic amount of datagrids that are brought in programmatically each one on a new tabpage.
My problem is that I need to change the Header of each column. I have tried doing it through a method
DataGridForSupplier.Columns[0].Header = "123";
but that keeps crashing with an error:
Index was out of range. Must be non-negative and less than the size of the collection
Turns out the problem is that the grid wasn't finished loading. So after waiting for all tabpage to load and add data to all the grids , even then the code
DataGridForSupplier.Columns[0].Header = "123";
would still crash. If the tabs are left to load on their own with no header tampering then the datagrid shows fine.
I would just LOVE to do this in XAML problem is that seeing that I don't know how many grids will load at run time I tried doing this at the back. So I'm open to any solution at this point. I tried finding a solution that would incorporate something that would 'theme' all the datagrids. Luckily all the datagrids headers will repeat across all tabs. So header 1 on tabpage 1 - 10 will be the same. Header 2 on tabpage 1 - 10 will be the same
Something like
<DataGridTemplateColumn.Header>
<TextBlock Text="{Binding DataContext.HeaderNameText, RelativeSource=>> RelativeSource AncestorType={x:Type DataGrid}}}" />
</DataGridTemplateColumn.Header>
but this needs to repeat for every Grid. This seems to escape me at the moment.
Any help would be welcome.
A rather lengthy answer, but this solution does not require any additional libraries, 3rd party tools, etc. You can expand it as you want later such as for adding hooks to mouse-move/over/drag/drop/focus, etc. First the premise on subclassing which I found out early in my learning WPF. You can not subclass a xaml file, but can by a .cs code file. In this case, I subclassed the DataGrid to MyDataGrid. Next, I created an interface for a known control type to ensure contact of given functions/methods/properties. I have stripped this version down to cover just what you need to get.
The interface below is just to expose any class using this interface MUST HAVE A METHOD called MyDataGridItemsChanged and expects a parameter of MyDataGrid.. easy enough
public interface IMyDataGridSource
{
void MyDataGridItemsChanged(MyDataGrid mdg);
}
Now, declaring in-code a MyDataGrid derived from DataGrid. In this class, I am adding a private property of type IMyDataGridSource to grab at run-time after datagrids are built and bound.
public class MyDataGrid : DataGrid
{
// place-holder to keep if so needed to expand later
IMyDataGridSource boundToObject;
public MyDataGrid()
{
// Force this class to trigger itself after the control is completely loaded,
// bound to whatever control and is ready to go
Loaded += MyDataGrid_Loaded;
}
private void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
{
// when the datacontext binding is assigned or updated, see if it is based on
// the IMyDataGridSource object. If so, try to type-cast it and save into the private property
// in case you want to add other hooks to it directly, such as mouseClick, grid row changed, etc...
boundToObject = DataContext as IMyDataGridSource;
}
// OVERRIDE the DataGrid base class when items changed and the ItemsSource
// list/binding has been updated with a new set of records
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
// do whatever default behavior
base.OnItemsChanged(e);
// if the list is NOT bound to the data context of the IMyDataGridSource, get out
if (boundToObject == null)
return;
// the bound data context IS of expected type... call method to rebuild column headers
// since the "boundToObject" is known to be of IMyDataGridSource,
// we KNOW it has the method... Call it and pass this (MyDataGrid) to it
boundToObject.MyDataGridItemsChanged(this);
}
}
Next into your form where you put the data grid. You will need to add an "xmlns" reference to your project so you can add a "MyDataGrid" instead of just "DataGrid". In my case, my application is called "StackHelp" as this is where I do a variety of tests from other answers offered. The "xmlns:myApp" is just making an ALIAS "myApp" to the designer to it has access to the classes within my application. Then, I can add
<Window x:Class="StackHelp.MyMainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myApp="clr-namespace:StackHelp"
Title="Main Window" Height="700" Width="900">
<StackPanel>
<!-- adding button to the main window to show forced updated list only -->
<Button Content="Refresh Data" Width="100"
HorizontalAlignment="Left" Click="Button_Click" />
<myApp:MyDataGrid
ItemsSource="{Binding ItemsCollection, NotifyOnSourceUpdated=True}"
AutoGenerateColumns="True" />
</StackPanel>
</Window>
Now, into the MyMainWindow.cs code-behind
namespace StackHelp
{
public partial class MyMainWindow : Window
{
// you would have your own view model that all bindings really go to
MyViewModel VM;
public MyMainWindow()
{
// Create instance of the view model and set the window binding
// to this public object's DataContext
VM = new MyViewModel();
DataContext = VM;
// Now, draw the window and controls
InitializeComponent();
}
// for the form button, just to force a refresh of the data.
// you would obviously have your own method of querying data and refreshing.
// I am not obviously doing that, but you have your own way to do it.
private void Button_Click(object sender, RoutedEventArgs e)
{
// call my viewmodel object to refresh the data from whatever
// data origin .. sql, text, import, whatever
VM.Button_Refresh();
}
}
}
Finally to my sample ViewModel which incorporates the IMyDataGridSource
public class MyViewModel : IMyDataGridSource, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public ObservableCollection<OneItem> ItemsCollection { get; set; }
= new ObservableCollection<OneItem>();
public void Button_Refresh()
{
ItemsCollection = new ObservableCollection<OneItem>
{
new OneItem{ DayName = "Sunday", DayOfWeek = 0},
new OneItem{ DayName = "Monday", DayOfWeek = 1},
new OneItem{ DayName = "Tuesday", DayOfWeek = 2},
new OneItem{ DayName = "Wednesday", DayOfWeek = 3},
new OneItem{ DayName = "Thursday", DayOfWeek = 4},
new OneItem{ DayName = "Friday", DayOfWeek = 5 },
new OneItem{ DayName = "Saturday", DayOfWeek = 6 }
};
RaisePropertyChanged("ItemsCollection");
}
// THIS is the magic hook exposed that will allow you to rebuild your
// grid column headers
public void MyDataGridItemsChanged(MyDataGrid mdg)
{
// if null or no column count, get out.
// column count will get set to zero if no previously set grid
// OR when the items grid is cleared out. don't crash if no columns
if (mdg == null)
return;
mdg.Columns[0].Header = "123";
}
}
Now, taking this a step further. I don't know how you manage your view models and you may have multiple grids in your forms and such. You could create the above MyViewModel class as a smaller subset such as MyDataGridManager class. So each datagrid is bound to its own MyDataGridManager instance. It has its own querying / populating list for the grid, handling its own rebuild column headers, mouse clicks (if you wanted to expand), record change selected, etc.
Hope this helps you some. Again, this does not require any other 3rd party libraries and you can extend as you need. I have personally done this and more to the data grid and several other controls for certain specific pattern handling.

How to access the Items of a User Control

I have a C# Form that prints multiple instances of a User Control. Let's say that the form prints 5 instances of the User Control (Please see the link attached). How can I store/save the data inputted in all User Controls? Thanks
Here is the screenshot of the C# Form:
You'll have to store the User Controls when you instantiate them in a List or something.
You could have a class like this:
class SomeUC : UserControl
{
public SomeUC()
{
}
// A public method.
public string GetData()
{
return textBox1.Text;
}
}
Where textBox1 is the Name of a TextBox in your SomeUC
And then inside your main or something.
// Instantiate a List that will hold your UserControls, this has to be outside all methods
List<SomeUC> list = new List<SomeUC>();
// And now when you want to build your UCs
// Instantiate your UserControl
SomeUC uc1 = new SomeUC();
// Store your UserControl in a List or something (Can't help you with that)
list.Add(uc1);
Add as much as you want.
A List is not the only way you can do that, but since you don't know how many UserControls you're going to build beforehand, it makes since to use a List.
And then you can access them from the list by their index.
SomeUC uc1 = list[0];
string data = uc1.GetData();
This is an example of accessing one control (the TextBox) in your SomeUC. For other classes (such as the ComboBox) the interaction is different. Meaning you won't have a Text property in the ComboBox. You'll have to figure out things like that on youself. A little research is what it takes. You can always come back if you couldn't find a solution for something.
You can create a property like this for each item in user control.
public string DG
{
get
{
return txtDG.Text;
}
set
{
txtDG.Text = value;
}
}
Then you can access the control value by using following line in your form.
supposed you have created a usercontrol MyControl and you have placed some object of this control in FlowLayoutPenal (pnlFLP).
To get value from control
string DG = ((MyControl)pnlFLP.Controls[0]).DG;
To set value in control
((MyControl)pnlFLP.Controls[0]).DG = "1";
Try this code for accessing user control in the page
Dim txtName As TextBox = TryCast(UserControlName.FindControl("txtName"), TextBox)

How to make Microsoft.VisualBasic.PowerPacks.DataRepeater instantly update bound data?

Here I'm talking about Windows Forms Application written in C#. Consider a simple model
class Labelled
{
private string label;
public string Label
{
get { return label; }
set
{
if (label != value)
{
string message = String.Format(
"Label changed from {0} to {1}",
label, value
);
MessageBox.Show(message);
label = value;
}
}
}
public Labelled(string label)
{
this.label = label;
}
}
class Model
{
public Labelled SingularLabelled { get; set; }
public List<Labelled> ListedLabelled { get; set; }
public Model()
{
SingularLabelled = new Labelled("Singular");
ListedLabelled = new List<Labelled>();
for (int i = 1; i <= 10; ++i)
ListedLabelled.Add(new Labelled("Listed " + i.ToString()));
}
}
We have a simple class Labelled with string property Label and class Model with member SingularLabelled of type Labelled and ListedLabelled which is a list of Labelled's.
Now I want to display the data to the user. Here is my setup:
The main window has a TextBox for displaying SingularLabelled.Label and a DataRepeater from the Visual Basic PowerPacks to display labels of the elements of ListedLabelled. The ItemTemplate of the DataRepeater consists of just a single TextBox.
Let's focus on one way binding, namely I want the underlying data to be updated when the User changes the contents of a text box. The Label property of the Labelled raises a notification in form of a message box, so I can get to know exactly when the data is being updated. Now the arrows represent bindings. Blue arrows stand for data source and the red ones for data members. An instance of Model is created and bound to the modelBindingSource in the constructor of the main window form.
And here we come to a very important thing. I want the data to be updated immediately in sync with what the User is typing, so I made sure that the data source update modes of the data bindings are set to OnPropertyChanged. The generated code that might be of interest here is
this.singularTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.modelBindingSource, "SingularLabelled.Label", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
this.listedTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.listedLabelledBindingSource, "Label", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
And this is working as expected when typing into the text box of SingularLabelled but the text boxes within DataRepeater trigger the update only when they loose focus. I want them to behave like the singular one. Ideal solution would be to do it using the designer. Does anyone know how to do this?
Above is a sample of the program working. Notice how SingularLabelled's label is updated every character put in and the members of ListedLabelled get the whole edited chunk updated after the corresponding text box looses focus.
We were able to overcome this limitation of DataRepeater by simulating the Tab key.
private void listedTextBox_TextChanged(object sender, EventArgs e)
{
//simulate tab key to force databinding
SendKeys.Send("{TAB}");
SendKeys.Send("+{TAB}");
}

Adding Items to a ListBox

I have a WPF main window, which contains a toolbar with buttons and a tabcontrol that is displaying a page with a listbox. The page is hosted on a frame, and the frame is set on the tab I selected.
When I click on a button on my toolbar, a new window pops up with a textbox and a submit button. When I press the submit button, I want to insert the textbox contents into the listbox that's on the main window. However, nothing displays in the listbox. I tried listbox.Items.Add() but it still won't display.
public partial class AddNewCamper : Window
{
public AddNewCamper()
{
InitializeComponent();
}
private void btnNewSubmit_Click(object sender, RoutedEventArgs e)
{
CampersPage c;
// Converting string to int b/c thats what camper() takes in.
int NewAge = Convert.ToInt16(txtNewAge.Text);
int NewGrade = Convert.ToInt16(txtNewGrade.Text);
// Create a new person
Camper person = new Camper(NewAge, NewGrade, txtNewFirstName.Text);
txtNewFirstName.Text = person.getName();
// Trying to add the first name of the person to display on the listbox of another window.
c.testListBox.Items.Add(txtNewFirstName.Text);
}
You can follow any of the following approaches. But based on your comments I guess solution 3 suits you.
1) Try initializing c first. You can't use an object without allocating memory for it.
2) If you want to use the same object, use the reference of the object created in the MainWindow
in the required class.
something like this should work:
CampersPage c = [reference to CampersPage object in MainWindow]
then add items to your listbox
3) If you want to use the listbox object, make your CampersPage Class static.
Making it static would not require you to initialize your class explicitly.
public static CampersPage {
// do something here
}
Make sure that you declare your listbox in CampersPage as public.
Then in the class requiring your listbox defined in CampersPage, do the following
CampersPage.testListBox.Items.Add(txtNewFirstName.Text);
4) If the classes are in the same namespace, you can declare listbox as a global public property and access it from rest of the classes in the same namespace.

Categories

Resources