Binding from just created class - c#

I have a class called Channel, that has properties: channelName, (more but irrelevant for this question) , and two List<double> (xValues, yValues).
public class Channel
{
public string channelName;
public List<double> xValues= new List<double>();
public List<double> yValues= new List<double>();
}
I also have a class called File, there are properties as: fileName, ObservableCollection<Channel> listOfChannels. File has a method called read(); that creates internally objects of class Channel for reading the data, depending of the data there will be a variable number of channel, and stores in the Lists xValues and yValues the data.
public class File
{
public string fileName{ get; set; }
public ObservableCollection<Canal> channels{ get; set; }
public void read() {//stuff}
}
In my program I've created a ComboBox that is binded to that data this way:
<ComboBox x:Name="comboFile"
ItemsSource="{Binding myListOfChannels}"
DisplayMemberPath="channelName"
SelectedValuePath="channelName"
SelectedItem="{Binding selectedChannel}" />
Where myListOfChannels and selectedChannel are defined as:
public ObservableCollection<Canal> myListOfChannels { get; set; }
public Canal selectedChannel { get; set; }
I instantiated them properly later in the code.
When I click a button the file loads and it creates a new object of class File. This is my exampleFile.
private void openButton_Click(object sender, RoutedEventArgs e)
{
File exampleFile= new File();
Channel exampleChannel= new Channel();
exampleFile.fileName= #"C:\Users\Path\myFile.txt"; //I haven't created OpenDialog yet
exampleFile.read();
myListOfChannels = new ObservableCollection<Channel>();
foreach (Channel mychannel in exampleFile.channels)
{
myListOfChannels .Add(mychannel);
}
selectedChannel = exampleFile.channels[0];
comboFile.DataContext = this;
}
This is a translation from other language, there can be slight errors in syntax, but it works.
Please, I don't want a complete redesign of this, there are other constrains.
My question is about if it's possible to remove the redundant assignation (myListOfChannels and selectedChannel, the foreach loop, etc) and directly bind the data from my just created exampleFile, something like this:
<ComboBox x:Name="comboFile"
ItemsSource="{Binding exampleFile.channels}"
DisplayMemberPath="exampleChannel.channelName"
SelectedValuePath="exampleChannel.channelName"
SelectedItem="{Binding selectedChannel}" />
I'm extremely newbie here, so if you could actually help me with the writing that would be great. I've read several tutorials of data-binding but I can't figure out this.
BTW. This may be important: All this code is inside a UserControl. In my MainWindow.xaml is only a instance of that UserControl.
I've tried my best to explain what I want but if something isn't clear just ask it. Thank you.

When you use a Path on a binding (e.g. {Binding Something} is equivalent to {Binding Path=Something}) or e.g. DisplayMemberPath, it must refer to a property. Not a field (e.g. public string Something;), or a local variable (e.g. void SomeMethod() { string something; }), but a public property (e.g. public string Something { get; set; }).
In your code, exampleFile and exampleChannel are, as far as I can see, local variables. Also, exampleChannel doesn't appear to be used. You could fix it with something like this:
public File ExampleFile { get; set; }
private void openButton_Click(object sender, RoutedEventArgs e)
{
ExampleFile = new File();
ExampleFile.fileName= #"C:\Users\Path\myFile.txt"; //I haven't created OpenDialog yet
ExampleFile.read();
myListOfChannels = new ObservableCollection<Channel>();
foreach (Channel mychannel in ExampleFile.channels)
{
myListOfChannels.Add(mychannel);
}
selectedChannel = ExampleFile.channels[0];
comboFile.DataContext = this;
}
(as a convention, properties in .NET use PascalCase, so it's ExampleFile, not exampleFile)
Also of note: if the property can change, and you want the binding to automatically update when that happens, you should implement INotifyPropertyChanged on the class(es) you're binding to (in this case, that looks like File, Channel, and MainWindow).

Related

Binding Label's Text to a Variable in Xamarin forms c#

So Im trying to creat a simple app like a shopping app. so I have categories and multiple items for each category, and when you get to choose an item then you will have the posibility to increase how many you need or delete the item. For exemple I chosed three items, so my cart have 3 items where each one have an Add button and a delete button. When I hit the add button the number of the items shown should increase and so on.
so what I've done so far is creating a JSON file that having all my categories, and once I hit a category I get to deserialize another JSON file that have all my items, so the items shown depends on the category I chosed of course.
Now each time i choose an item it get added to the cart and shown on the bottom page with a + and - buttons and so on.
so I created a category class to deserialize my json, and an objets class to deserialize my Item's json. I implememted the INotifyChangedProperty in the objets class so that I can keep showin whenever the number of a chosen item get increased, so basicly thats my ViewModel, but I guess that it's like that I need a ViewModel of each created item ? so I guess what I really need to use is the ObservableCollection ..
I hope I explained everything well, and waiting for your feedbacks about if Im doing it right or wrong and how should i proceed to get what I want. thank you so much
the problems is that to set the bindingcontext to my "Objets" Class I have to put the arguments in it, and then my Label well get a precised value ... what should I do ?
I do one sample about your model, you can take a look:
<ContentPage.Content>
<StackLayout>
<Label x:Name="label1" />
<Button
x:Name="btn1"
Clicked="Btn1_Clicked"
Text="change value" />
</StackLayout>
</ContentPage.Content>
public partial class Page15 : ContentPage
{
public Objets model { get; set; }
public Page15()
{
InitializeComponent();
model= new Objets("test 1", 1.001f, " test11111", 12);
this.BindingContext = model;
label1.SetBinding(Label.TextProperty, "nbr_objet");
}
private void Btn1_Clicked(object sender, EventArgs e)
{
model.nbr_objet = 20;
}
}
public class Objets : INotifyPropertyChanged
{
public string Designation { get; set; }
public float Prix { get; set; }
public string imageUrl { get; set; }
private int Nbr_Objet;
public int nbr_objet
{
get { return Nbr_Objet; }
set
{
Nbr_Objet = value;
RaisePropertyChanged("nbr_objet");
}
}
public Objets(string Designation, float Prix, string imageUrl, int Nbr_Objet)
{
this.Designation = Designation;
this.Prix = Prix;
this.imageUrl = imageUrl;
this.Nbr_Objet = Nbr_Objet;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Update:
but I guess that it's like that I need a ViewModel of each created item ? so I guess what I really need to use is the ObservableCollection ..
You said that you have three categories, and each category have many items, If you display these in ListView, category is used as Group header, and I suggest you can use the same model for different item for different categories, then add in Observablecollection, because it have implemented INotifyPropertyChanged interface.
About ListView group, you can take a look:
https://github.com/xamarin/xamarin-forms-samples/tree/master/UserInterface/ListView/Grouping
If you still have another question, I suggest you can create new thread to ask, because this thread is very long.
Please remember to mark the helpful reply as answer, thanks.
to set a binding programatically
// set the BindingContext for the page
this.BindingContext = new MyViewModel();
// Title is a public property on MyViewModel
myLabel.SetBinding(Label.TextProperty, "Title");
in order for the UI to update when the VM is changed, the VM needs to implement INotifyPropertyChanged
This is some guidance that might help with your problem. Your code is messy and I think that is causing your confusion (you have several things named very similarly).
int Nbr_Objet;
public int nbr_objet { get{...} set {...}}
this.Nbr_Objet= Nbr_Objet;
this shows me that you are setting your member variable Nbr_Objet directly, when you do that the property change notification doesn't fire - you need to assign the value through the public nbr_objet for that to happen.
I'd suggest you define the binding in XAML, and make sure you bind to the property nbr_objet, not the private member variable (field) Nbr_Objet.
If you want to avoid confusion, follow the C# coding standard and name your member variable _nbrObjet, and camel case your property name public int NbrObjet { get {....

How to fill a Combobox from foreach in a different class

I am trying to fill a comboBox and a secondary form from a separate class file and I think I have the basics wrong.
The following is shortened to show the bones of what I have and what I think I may be doing wrong. I am not sure if I should be using List<> to populate the comboBox or an Array and suspect in either case my method declaraction is wrong and I cannot find a reference to the comboBox to populate from within the foreach loop.
OK the program.cs for my settings Form. This is not the main form but is the one I am looking to populate when a user select the comboBox.
Program.cs
class Settings
{
public partial class Settings : Form
{
// a bunch of String declarations used throughout
public Settings()
{
InitializeComponent();
}
}
}
The method within a class in a separate file is
Functions.cs
class functions
//This is a separate class to the Settings Form but same namespace.
{
//some global variables here
public string getDomain(string webURL)
{
//more variable declarations
//webURL is a value from the Settings Form
//code to send query to website, get the response and filter the response.
//This is the response filter
foreach (XmlNode node in xmlDoc.SelectNodes("//DAV:domains/DAV:domain", nsmgr))
{
strNode = node.InnerText;
responseString += strNode + " ";
list.Add(strNode);
//I would like to simply Add.Items(strNode) to the Settings.Form.cbxDomains but not as simple as this.
}
//this returns all the correct information as a space separated string.
return responseString;
}
}
From Update UI from a different thread in a different class
I thinks there are 2 things I should do.
1. change the form initialisation from InitilizeComponents() to
settingsWindow = new MyForm();
Application.Run(form);
Then simply call Settings.settingsWindow.cbxDomain.Add>items(responseString);
But do I also need to change the actual method to something like
public void List<String> getDomain(string webURL)
I am so confused. Most examples show it the other way of updating the class from the combo not the other way and some say create it as an array.
I actually think it could even be trimmed down further into one or 2 lines instead of the foreach, but that is way beyond my skillset at this time.
Here is how you could populate your combobox from another form.
I will use example since I do not know you code structure but you will get the point.
public class User //Custom generic class
{
public int _Id { get; set; }
public string _Name { get; set; }
}
public class Functions
{
public static List<User> PopulateComboboxWithUsers()
{
List<User> list = new List<User>();
foreach(var something in somethingBig) //You can change this if you re reading form XML with it's variables or something else
{
list.Add(new User { _Id = something.Id, _Name = something.Name };
}
return list;
}
}
public class Settings
{
public Settings()
{
InitializeComponents();
comboBox1.DataSource = Functions.PopulateComboboxWithUser();
comboBox1.DisplayMember = "_Name";
comboBox1.ValueMember = "_Id";
}
}
Other approach is to do the same function expect you will pass comboBox to it and you will do assigning inside it but I think this is more flexible.

ObservableCollection not showing up in UI

I'm trying to display list of items in XAML. I get the list from public API, convert it to the class I need and then I want to display it.
public static async Task PopulateListAsync(ObservableCollection<MyClass> myList) {
var listContainer = await GetListAsync();
foreach (var item in listContainer) {
//converting from one class to another, editing some properties and such
myList.Add(item );
}
}
and on the MainPage.cs I had
public ObservableCollection<MyClass> Value { get; set; }
public MainPage() {
this.InitializeComponent();
Value = new ObservableCollection<MyClass>();
}
private async void Page_Loaded(object sender, RoutedEventArgs e) {
await PopulateListAsync(Value);
}
And I displayed in the XAML fine.
But then I wanted to introduce filtering. So I get the data, convert them to some class and insert them to a list, which I then filter with LINQ (seems easier then filtering in ObservableCollection).
Basically I replaced the PopulateListAsync() with FormatListAsync() which instead of inserting the data directly into the ObservableCollection<>, returns a List<>. Then I have a "middle man" function
public static async Task PopulateListAsync(ObservableCollection<MyClass> myList) {
myList = new ObservableCollection<MyClass>(await FormatListAsync());
//filtering itself isn't implemented yet, but it would be placed here
}
I probably could just loop trough mylist and add it one by one into the ObservableCollection<>, but I feel like there surely is a better way.
I think I'm supposed to implement some PropertyChanged event or something like that, but I tried a few (this one for example), unsuccessfully. I don't think I quite understand how to implement it.
If you are assign new value for method parameter then you just change reference's copy to the collection and don't change source reference. You can read more about passing reference types as method parameters on MSDN.
Also, if you will change property that not implements INotifyPropertyChanged itself then you'll have no changes in UI because your view doesn't know about the changes.
In the simple and easy way you can manipulate source collection instead of creating new one. Just do something like
public static async Task PopulateListAsync(ObservableCollection<MyClass> myList)
{
// newList can be an List<MyClass> type, not ObservableCollection
var newList = await FormatListAsync();
// change displayed list with new data
myList.Clear();
foreach(var newValue in newList)
myList.Add(newValue);
}
The other option, you can implement INotifyPropertyChanged for your ViewModel and raise PropertyChanged event in the setter of Value property:
private ObservableCollection<MyClass> _value;
public ObservableCollection<MyClass> Value
{
get
{
return _value;
}
set
{
// I hope this line of code will convince you to give more clear variable name
if(value != _value)
{
_value = value;
NotifyPropertyChanged(nameof(Value));
}
}
}
Also, you'll need to assign Value directly in the PopulateListAsync():
public static async Task PopulateListAsync()
{
Value = new ObservableCollection<MyClass>(await FormatListAsync());
}

Binding combobox to another combobox

I have a problem with binding combobox to another combobox. I'm trying to dynamically pass a parameter (id) from first combobox to the method of initiating second combobox. For example, if I selected the first item in first combobox, then second combobox will initialize with parameter that selected from first combobox.
XAML:
<ComboBox Name="ItServiceCmbox" ItemsSource="{Binding ItServiceMetricsNames}" DisplayMemberPath="ServiceName" SelectedValuePath="ServiceId" />
<ComboBox Name="MetricCmbox" ItemsSource="{Binding SelectedItem.MetricId, ElementName=ItServiceCmbox}" DisplayMemberPath="MetricName" SelectedValuePath="MetricId"/>
C#:
public partial class MainWindow : Window
{
readonly MetricsValuesHelper _metricsValuesHelper = new MetricsValuesHelper(new Repository());
public static int SelectedService;
public static int SelectedMetric;
public ObservableCollection<ItServiceMetricsNames> ItServiceMetricsNames { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
SelectedService = Convert.ToInt32(ItServiceCmbox.SelectedItem);
ItServiceMetricsNames = new ObservableCollection<ItServiceMetricsNames>();
ItServiceMetricsNames.Add(new ItServiceMetricsNames()
{
ServiceId = _metricsValuesHelper.GetServiceId(),
ServiceName = _metricsValuesHelper.GetServiceName(),
MetricId = _metricsValuesHelper.GetMetricId(SelectedService),
MetricName = _metricsValuesHelper.GetMetricName(SelectedService)
});
}
}
And ItServiceMetricsNames class:
public class ItServiceMetricsNames
{
public List<int> ServiceId { get; set; }
public List<string> ServiceName { get; set; }
public List<int> MetricId { get; set; }
public List<string> MetricName { get; set; }
}
Is it possible? Thanks for any answers!
This is a messy, naive implementation I did last year that seemed to work. There's definitely a better way out there. Instead of trying to do any actual binding in my xaml I made event handlers. You may create event handlers for ComboBoxes that are triggered whenever the sending ComboBox loses focus, closes it's DropDown, changes selection, etc.
If you want one ComboBox dependent on another, you may make the dependent ComboBox disabled until a selection is made in the independent ComboBox. Once a selection is made, you populate and enable the dependent ComboBox with the appropriate data.
Event handlers in your code will look something like this:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Independent ComboBox is the sender here
ProcessComboBoxes(sender as ComboBox);
}
The ProcessComboBoxes method will look different depending on what you're trying to do. But, essentially, it will identify the target/dependent ComboBox that you want to conditionally populate -- do this either with a Dictionary that maps from ComboBox to ComboBox or something you find suiting. After identifying the target, you will clear any items previously added, and then repopulate with your new ones. Below is a method in pseudocode (practically).
private void ProcessComboBoxes(ComboBox senderBox)
{
ComboBox dependentBox = lookupDependent[senderBox];
var itemType = itemTypes[senderBox.selectedIndex];
var listOfItemsNeeded = lookupItemsByType[itemType];
dependentBox.Items.Clear();
foreach (string item in listOfItemsNeeded){
dependentBox.Items.Add(item);
}
dependentBox.IsEnabled = true;
}
Don't forget to add your eventhandlers to your xaml. Make sure to pay close attention to the call hierarchy of events and determine when exactly you want your dependent ComboBox to be repopulated.

How to add items to a list in C# using a class containing public variables

I hope you can help out a fellow programmer. Basically, I want the user input from the Rich Text Box (taskNameRTB) to be assigned to the taskName; string variable in my class taskStructure which is in form1 shown below:
public partial class Form1 : Form
{
public class taskStructure
{
public string taskName;
public string taskDescription;
public int Priority;
public string dateAndTime;
}
public List<taskStructure> TasksArray = new List<taskStructure>(); //Declared a list data structure
In my second form which is where the user enters everything related to the task, I want to send this information to the list after the 'Create Task' button has been clicked:
private void createTaskBtn_Click(object sender, EventArgs e)
{
Form1 welcomeForm = new Form1();
welcomeForm.TasksArray[0].taskName = taskNameRTB.Text;
welcomeForm.TasksArray[0].taskDescription = taskDescRTB.Text;
}
However, when I do this I get a ArgumentOutOfRangeException and I do not understand why. I have also tried these:
welcomeForm.TasksArray[0].Add(taskDescRTB.Text);
welcomeForm.TasksArray.Insert(0, taskNameRTB.Text);
welcomeForm.TasksArray.Add(taskDescRTB.Text);
taskNameRTB.Text = welcomeForm.TasksArray[0].taskName;
But the ones that run come up with the same error ArgumentOutOfRangeException and some of them don't work, such as:
welcomeForm.TasksArray[0].Add(taskDescRTB.Text);
I'm aware that the list has not been initialized, but how can I initialize it when it doesn't allow me to initialize it with user input...
Any light you can shed on this will be really helpful
Kind Regards,
Kieran
You need to add a new taskStructrue to the list.
welcomeForm.TasksArray.Add(new taskStructure
{
taskName = taskDescRTB.Text,
taskDescription = taskDescRTB.Text
});
But personally I'd rewrite that class to follow naming conventions and to use properties instead of public fields.
public class TaskStructure
{
public string TaskName { get; set; }
public string TaskDescription { get; set; }
public int Priority { get; set; }
public string DateAndTime { get; set; }
}
have you tried
welcomeForm.TasksArray.Add(new taskStructure(taskDescRTB.Text));
I don't know what taskStructure is, but you need to fill TasksArray with types of it.
Your TaskStructure is a class, and you are putting all TaskStructure objects into a list,
public List<taskStructure> TasksArray = new List<taskStructure>(); //Declared a list data structure
Does your Form1() have a constructor that calls InitializeComponents()?
If so, you could try adding TasksArray = new List<taskStructure>() right below InitializeComponents(), because it looks like you're trying to access the list data structure that hasn't been initialized with new.
Alternatively
As another user noted, you can create a constructor class for TaskStructure like this:
public TaskStructure(RTB rtb1, rtb2, rtb3) //where RTB is the rich text box type
{
taskName = rtb1.text;
taskDescription = rtb2.text;
//and so on.
}
Then you can do TaskArray.add(new TaskStruture(rtb1,rtb2,rtb3).
Thrid Edit
Just realized your TaskArray is actually a List, which in C# (and Java), you cannot access it with an index like TaskArray[0], you have to use getter and setter methods, which in this case is TaskArray.add(), and TaskArray.get(0), you're getting ArgumentOutOfRangeException because you're trying to access a List using square indexes like this --> [0]. You can actually access a list doing list1, as pointed out by another user.
Here's a good tutorial on C# lists, by DotNetPerls

Categories