I am in need of some guidance for the following design.
I have a tab control that contains various group boxes. Within the group box, there are specific controls that relates to that group box. For example:
Now whenever a change is made to any control in the group box, the value for the control needs to be tracked because at the end of the application run cycle, the control data will need to be saved to a file that contains that value. An example file is:
HOT_STANDBY_FEATURE_ENABLE [val from control here]
HEART_BEAT_DIGITAL_OUTPUT [val from control here]
....
A design that I have in mind has another that has just properties that the group box form sets whenever a ValueChanged event occurs on a control.
Example code:
class ConfigurationValues
{
public int HotStandbyValue { get; set; }
public int HeartBeatDigitalOutputValue { get; set; }
//...add all other controls here
}
The downside that I see to this is that there are 40 controls on that tab page, so I'd have to manually type each property. When the file needs to be generated at the end of the application run cycle, I have a method that gets the value of the control need.
Example:
private void GenerateFile()
{
string[] file =
"HOT_STANDBY_FEATURE_ENABLE " + ConfigurationTabSettings.HotStandbyValue;
}
Another design consideration I need to make is that whenever a user clicks "Open Configuration File", the values from the file from disk need to be loaded into the properties so the form can take that data on startup and populate the controls within the group boxes with their respective values.
Any suggestions on this design would be greatly appreciated. I know this is not the most efficent way to do this and am not the most experienced programmer, so any Google keywords I can search for would be great also.
You could xml serialise and xml deserialise your ConfigurationValues class rather than writing manual "generate file" and "read file" methods
http://support.microsoft.com/kb/815813
You'll need to bind the controls Text or Value properties to the properties in your ConfigurationValues class e.g.
ConfigurationValues cv = Repository.ReadConfigValues();
numPulseFilterDelay.DataBindings.Add("Value", cv, "PulseFilterDelay");
// Add the rest of your control bindings here
on the btnSave_Click() of your Form, end the current edit on the form and save the config values
void btnSave_Click(object sender, EventArgs e)
{
BindingContext[cv].EndCurrentEdit(); // Commits all values to the underlying data source
Repository.SaveConfigValues(cv);
}
In your repository class you'll need methods to Load() and Save() the data. You can put XmlSerialization code in here, or write your own format (depending on your requirements)
public class Repository
{
public static ConfigurationValues LoadConfigValues()
{
var cv = new ConfigurationValues();
string[] lines = File.ReadAllLines("values.cfg");
foreach (string cfg in lines)
{
string[] nameValue = cfg.Split(new char[] { ' ' } ); // To get label/value
if (nameValue[0] == "HOT_STANDBY_FEATURE_ENABLE")
cv.HotStandbyFeatureEnable = nameValue[1];
else if (nameValue[0] == "SOME_OTHER_PROPERTY")
cv.SomeOtherProperty = nameValue[2];
// Continue for all properties
}
return cv;
}
public static void SaveConfigValues(ConfigurationValues cv)
{
var builder = new StringBuilder();
builder.AppendFormat("HOST_STANDBY_FEATURE_ENABLE {0}\r\n", cv.HostStandbyFeatureEnable);
// Add the rest of your properties
File.WriteAllText("values.cfg", builder.ToString());
}
}
Related
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.
I'm currently working on a new GUI that has a listbox as a crucial element in it. So far I've managed to display and select multiple items in this listbox, which wasn't that big of a deal.
My goal is to have several .cs files(maybe in further expansion als vb script files) in a folder within the project, which are properly displayed in the main view listbox and will be executed if the corresponding item in the listbox is selected.
So far I've tried to build the listbox and all the other GUI stuff (buttons, text,...) and connected the listbox with a bindable collection of a script model(which is a own class for testing purposes at the moment and should be replaced with the correct .cs files)
In the code below you can see the work around with this custom class and the selection check for multiple listbox items.
private void Run_Click(object sender, RoutedEventArgs e)
{
//Show user the scripts which are being processed, these are the previous selected Scripts
List<string> selectedList = new List<string>();
foreach (ScriptModel selectedScript in MainListBox.SelectedItems)
{
selectedList.Add(selectedScript.Name.ToString());
}
//check if no Script was selected, and if so, just return/do nothing
if (selectedList.Count() == 0) { return; }
MessageBox.Show("The following Scripts will be processed: " + Environment.NewLine +
string.Join(Environment.NewLine, selectedList));
//Call the Connection for Data-Export
}
private BindableCollection<ScriptModel> _scriptscollection=new BindableCollection<ScriptModel>();
public BindableCollection<ScriptModel> ScriptsCollection
{
get { return _scriptscollection; }
set { _scriptscollection = value; }
}
I would like to know, how I can replace(or connect) these custom class with actual .cs files (which are some kind of scripts) in a project folder, so that I can display these file names and select the corresponding files for execution. (so the connection should work both ways)
I'm sorry if this question seems a bit weird and general, but I'm really confused about that.
I believe you have over complicated the matter. Here is the code that will find all of the .cs files in a directory and then upon selecting one in the ListBox will start that file.
It's hard to tell exactly what you're asking for but hopefully this helps.
XAML
<ListBox ItemsSource="{Binding ScriptFiles}" SelectedItem="{Binding SelectedScript}"/>
Code behind / ViewModel
public List<string> ScriptFiles => Directory.GetFiles(FilePath, "*.cs").ToList();
private string selectedScript;
public string SelectedScript
{
get { return selectedScript; }
set { selectedScript = value; Process.Start(value); }
}
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.
I've been using this programming style, that I've seen in an example and just started using it, because it does the job... I would like to know other programmers' opinion about it...
So the situation is when you have a GridView, or a control based on it like the RadGrid, and you want to keep track of a data table while you are adding, editing, reordering and deleting rows.
Using the session to hold the data table (or list of data) may not be the best solution, because the user may open two identical web pages… Using the ViewState to hold the data may be and option... I have been using an approach like the following:
public partial class DefaultPage : System.Web.UI.Page
{
protected DataLine DefaultDataLine()
{
DataLine dl = new DataLine();
dl = new DataLine();
dl.Number = 0;
dl.Title = "";
dl.Text = "";
return dl;
}
protected class DataLine
{
public int Number { get; set; }
public string Title { get; set; }
public string Text { get; set; }
}
protected static List<DataLine> tempLines;
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
tempLines = RadGridBindStartUpData();
}
}
protected void RadGrid1_NeedDataSource(object source, Telerik.Web.UI.GridNeedDataSourceEventArgs e)
{
RadGrid1.DataSource = tempLines;
}
protected void RadGrid1_InsertCommand(object source, Telerik.Web.UI.GridCommandEventArgs e)
{
GridEditableItem editedItem = e.Item as GridEditableItem;
List<DataLine> table = tempLines;
DataLine newRow = new DataLine ();
RadTextBox rtb;
rtb = (RadTextBox)editedItem.FindControl("RadTextBoxTitle");
newRow.Title = rtb.Text;
rtb = (RadTextBox)editedItem.FindControl("RadTextBoxDescription");
newRow.Description = rtb.Text;
RadNumericTextBox number = (RadNumericTextBox)editedItem.FindControl("RadNumericTextBoxNumber");
newRow.Number = number.Value.HasValue ? Convert.ToInt32(number.Value.Value) : 0;
table.Add(newRow);
}
// ...
So using a static List variable, of a custom object (class), declared in the code-behind of the Aspx page, and updating it whenever the data is edited.
What are your thoughts about this approach? Is it okay? How do you hold your table-format data for edition (prior to saving it in the database)?
Not exactly sure what you're going for, but using a static variable is probably not what you want to do. Static properties are shared across all user/threads, so all concurrent users would be editing the same data.
If you are just looking to persist a small data set across post-backs to the same page, use the ViewState instead. Just be mindful of potential performance issues if you plan on cramming lots of data into it.
It depends on what you're wanting to achieve
Viewstate will keep the data on that page - it won't be available on any other pages (or tabs, or windows)
Session will keep the data on the server, this means it will be available for any page the user is looking at (on your site) and it will keep it until the session times out.
Theres a lot of advtanges/disadvantages to either method, therefore you need to research your situation, here is a start.
You mentioned storing in the session, and how this could cause issues if the user opens up multiple copies of the page, etc...
We had a similar issue so I made a property in code behind on the page and on first page load (if not postback blah blah) I generate a new guid. Then I use the guid value as my session key and I know it'll be unique per page.
You could make a spify property like this...
Public ReadOnly Property SessionDataKey() As String
Get
If ViewState("SessionDataKey") Is Nothing Then
ViewState("SessionDataKey") = Guid.NewGuid()
End If
Return ViewState("SessionDataKey").ToString()
End Get
End Property
But in short, I just use the session.
Thank you very much for your replies! With your help, and some research, I see that both approaches, storing in session or using the static variable are indeed wrong, at least for the purpose I was using them... All your answers were helpful, and although I can only mark one as correct, I would like to leave my appreciation.
Well, for anyone stumbling across the same problem, here’s what I’ve implemented in my pages:
public partial class ScriptAdd : System.Web.UI.Page
{
private List<MyItem> tempMyItems
{
get
{
//if (ViewState["tempMyItemsList"] == null)
// ViewState["tempMyItemsList"] = new List<MyItem>();
return (List<MyItem>)ViewState["tempMyItemsList"];
}
set
{
ViewState.Add("tempMyItemsList", value);
}
}
protected void Page_Load(object sender, EventArgs e)
{
// ...
}
}
And then use it whenever I want to add / insert / update lines to my temporary list:
List<MyItem> table = tempMyItems;
table.RemoveAt(idx);
MyItem newRow = new MyItem ();
// ...
table.Insert(idx, newRow);
Finally, if intended, I store all the items in the database.
I found a question that I belive is what I was looking for, but there were certain things that I was not following in the answers. Therefore, I'd like to ask the question in a different way (Thanks for your patience). Here is the link I am referring to:
How to avoid duplicating logic on two similar WinForms?
OK. I have a dialog that I created. We've got controls for User Input, buttons to display other dialogs (to gain other input), etc. Aesthetically, I prefer the dialog with the controls laid out vertically. Anyhow, I was also thinking of creating a UserControl version of this dialog. This UserControl would have all the same controls, and all the same logic, but the controls would be laid out entirely different (more horizontal, then vertical).
So, I can't just create another (3rd) UserControl that I drop on the orignal form, and on the UserControl I want to create. (This 3rd UserControl would then contain all logic - thus, sharing between the two). I can't do this because of the different layouts.
I have no problem creating the two (Form, UserControl), with the controls laid out differently, but I don't want to 'cut-and-paste' all the logic from one to the other.
This does not seem like a case for MVP, or MVC. My model is the dialog itself. The dialog is intialized with some values, yes, but once initialized the "Model" becomes further User Input (which I then grab when they press the OK button).
Take for example this code (an event for one of my buttons on this dialog):
private void EditQuery_Click(object sender, EventArgs e)
{
try
{
EditQueryParameters();
}
catch (System.Exception ex)
{
// TODO: Write ErrMsg to Log file.
MessageBox.Show("Edit Query Parameters Error:\n\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void EditQueryParameters()
{
if (m_ReportType.QueryScoreDetails && optPickDetail.Checked)
{
// This brings up a different type of dialog
QueryDetails();
return;
}
// DateRange, StartDate, and EndDate are all saved from the last time
// I called this dialog
DateType DtType = new DateType(m_ReportType.DBDateRangeField,
m_DateRange, m_StartDate, m_EndDate);
// StartTime, EndTime too!
TimeType TmType = new TimeType(m_ReportType.DBTimeRangeField,
m_StartTime, m_EndTime);
List<AdvancedFilter> Filters = null;
if (lstAdvancedQuery.Items.Count > 0)
{
Filters = new List<AdvancedFilter>();
}
for (int i = 0; i < lstAdvancedQuery.Items.Count; ++i)
{
Filters.Add((AdvancedFilter)lstAdvancedQuery.Items[i]);
}
// QueryType is also saved from the last time I called QueryBuilder
QueryBuilder QryBuilder = new QueryBuilder(m_ReportType.DBCatalog, m_ReportType.DBTable,
m_QueryType, ref DtType, ref TmType, ref Filters);
// I am using Visual WebGUI, I have to do it this way
QryBuilder.Closed += new EventHandler(QryBuilder_Closed);
QryBuilder.ShowDialog();
}
I mean, I suppose I could have some "logic" class, which exposes something like:
public void EditQueryParameters(ref ReportType RptType, bool PickDetail,
string DateRange, DateTime StartDate, DateTime EndDate,
DateTime StartTime, DateTime EndTime, string QueryType)
{
if (ReportType.QueryScoreDetails && PickDetail)
{
// This brings up a different type of dialog
QueryDetails();
return;
}
DateType DtType = new DateType(ReportType.DBDateRangeField,
DateRange, StartDate, EndDate);
TimeType TmType = new TimeType(ReportType.DBTimeRangeField,
StartTime, EndTime);
// Yikes, more stuff to add to the signature of my method
// Will have to pull this outside the method and pass in Filters
List<AdvancedFilter> Filters = null;
if (lstAdvancedQuery.Items.Count > 0)
{
Filters = new List<AdvancedFilter>();
}
for (int i = 0; i < lstAdvancedQuery.Items.Count; ++i)
{
Filters.Add((AdvancedFilter)lstAdvancedQuery.Items[i]);
}
// QueryType is also saved from the last time I called QueryBuilder
QueryBuilder QryBuilder = new QueryBuilder(ReportType.DBCatalog, ReportType.DBTable,
QueryType, ref DtType, ref TmType, ref Filters);
// I am using Visual WebGUI, I have to do it this way
QryBuilder.Closed += new EventHandler(QryBuilder_Closed);
QryBuilder.ShowDialog();
}
There's a lot of set-up to use this method. I don't know, maybe I'm looking for something more .. "slick"?
On top of that, look at some (not all) of my init code (this is called from constructor or form_Load; it doesn't seem worth it to add this to the logic class, so that's all still "cut and paste" between the two):
private void InitializeUserDefinedTitle()
{
txtUserTitle.Text = m_UserTitle;
}
private void InitializePrintSelectionCriteria()
{
// Print Selection Criteria
chkSelectionCriteria.Checked = m_printSelectionCriteria;
}
private void InitializeTrendBy()
{
cmbTrend.Items.AddRange(Enum.GetNames(typeof(TrendBy)));
cmbTrend.SelectedIndex = (int)m_TrendBy;
cmbTrend.Visible = m_ReportType.TrendVisible;
lblTrend.Visible = m_ReportType.TrendVisible;
}
In summary, the original WinForm is a dialog that is intialized with data (constructor), is displayed to the user for input, when they OK the dialog that data is retrieved (and that data is stored outside the dialog, in member variables, for the next time they call the dialog - this is because we want to show what they last picked/entered).
That type of dialog I just described will also be a user control, and the logic should be shared between the two.
Thanks.
You can make two controls A and B, each containing the same buttons and/or other input controls arranged differently. Controls A and B will have identical properties, and events. The form (or third control) will contain the event handlers that allow the logic to be contained in only one place.
You can display either control A or B using the visible property or by adding one to the container.controls property, the container being the containing form or control.
And, for example, instead of having a handler for button1 in controls A and B that handles the complete logic of the button press, the handlers for button1 in control A and B would just raise an event that will be handled by the container of control A or B.
Instead of encapsulating the logic, I would encapsulate the layout. Use a property of the user control to specify which layout yout want. Then wherever it is (standalone form, one of three instances on the same form, whatever) you access it and specify the layout the same way.
As for how to encapsulate the layout, there are a bunch of possibilities. You could just do it programatically, i.e. write each version of the layout code. (The programamatic version would be cleaner if you used some kind of layout containers, like the Panels in WPF.) You could draw the layout in a designer, and copy the generated code. The different versions of the layout logic could be stuffed into private methods, or encapsulated into objects.