I am new to WPF and XAML. Sorry if this seems simple, but I have a set of classes:
Public class Master()
{
public int Id {get;set;}
public List<Student> Students { get; set; }
}
Public class Student()
{
public int Id {get;set;}
public string Name { get; set; }
}
I wish to display them in a datagrid, so I have created and configured a datagrid control on my page.
I have then bound my classes above using:
dataGrid.ItemsSource = result.Master.ToList();
This provides me with the list, but what I am trying to do is also display the student collection against each master row. At the moment all I get is (Collection) populated in the student record of the datagrid.
To move from comments to an answer:
I assume you know how to make another datagrid, since you already have 1 with correct bindings. Just copy paste another one and move it to one side.
For this other datagrid, let's call it dataGrid2.
On the first datagrid: add this to your xaml:
SelectionChanged="DataGrid_SelectionChanged"
and in code behind:
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selected = dataGrid.SelectedItem as Master;
dataGrid2.ItemsSource = selected.Students.ToList();
}
I haven't included everything, such as error checking and empty selection handling, etc. So you will need to implement it.
Related
Suppose I have one ObservableCollection that I insert, update and delete from.
public ObservableCollection<Item> AllItems {get;set;}
The Item class (which implements INotifyPropertyChanged) looks like this:
public class Item
{
public int Id {get;set;}
public int Kind {get;set;}
public string Text {get;set;}
}
I have this bound in my View in one ListView. Everything works, meaning if I delete or add or updates Items in the ObservableCollection from my ViewModel, they are updating in my ListView as expected.
But now I need to split the ListView in 2 ListViews in my View. The first ListView must display all items where Item.Kind=1. And the other ListView must have all items where Item.Kind=2.
I don't mind binding to two different ObservableCollections from my ViewModel, but I would really like a solution where I can just do:
var item = new Item { Id=22, Kind=1, Text="SomeText" };
AllItems.Add(item);
AllItems.First(x => x.Id==22).Text = "SomeOtherText";
AllItems.Remove(item);
And then these operations are automatically reflected in the 2 ListViews. So in the above example, the new Item is only viewed, updated and removed from the first ListView (where Item.Kind=1).
Is that possible?
EDIT: I should say that I have tried to use two CollectionViewSources with the same ObservableCollection as source. But that fails and does not seem to be the correct solution.
create two ListCollectionViews with different filters - one for each ListView
public ICollectionView Items_1 { get; private set; }
public ICollectionView Items_2 { get; private set; }
public ViewModel
{
Items_1 = new ListCollectionView(AllItems);
Items_1.Filter = o => (o as Item).Kind == 1;
Items_2 = new ListCollectionView(AllItems);
Items_2.Filter = o => (o as Item).Kind == 2;
}
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 {....
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.
I have a form with a datagridview which gets populated from an object in another class (basically this object acts like a database throughout the application). The selected row gets deleted when user clicks a button. The row should be deleted from dgv and also from that object.
There is also a search criteria which will reduce the number of rows displayed in dgv. Now when user opts for this search and selects a row from reduced number of rows, I am not able to handle the delete row logic.
Currently I am having the row number in INT variable and deleting the same instance in object list. I know this wont work when rows are reduced. Thanks in advance for your help.
In form :
private void btnDelete_Click(object sender, EventArgs e)
{
class obj=new class();
int row = dataGridView1.CurrentCell.RowIndex;
obj.deleteName(row);
dataGridView1.Rows.RemoveAt(row);
}
In class:
class Class
{
List<string> names=new list<string>();
public void deleteName(int index)
{
names.RemoveAt(index);
}
}
Here are some suggestions, using as much of your current code as possible.
Create a custom class to populate the DataGridView:
public class MyData
{
public int ID { get; set; }
public string Name { get; set; }
...
}
Then you can access the current item from the grid using:
var currentRow = (MyData)dataGridView1.CurrentRow.DataBoundItem;
obj.DeleteRecord(currentRow);
And use that record when deleting from the collection:
class Class
{
List<MyData> records = new List<MyData>();
public void DeleteRecord(MyData record)
{
records.Remove(record);
}
}
So, if I'm understanding correctly you're deleting it visually, but not in the source list? The way I'd do it so to have it as an System.Collections.ObjectModel.ObservableCollection<T> and perform all the adding/removing operations on that. Your view will synch with the list because ObservableCollection reports changes.
Instead of deleting objects based on Index, you may use the string itself to search for in the binded list in your class Class like below:
class Class
{
List<string> names=new list<string>();
public void deleteName(string name)
{
names.Remove(names.Single(x => x == name));
}
}
Then you can just rebind the DataGridView with the updated names list.
In C# Winforms DataGridView I am binding a List of class using the datagriview's datasource property. Is there a way to bind one of the class property to the datagriview's rowheader?
I didn't want to iterate all the rows and add row header values one by one because I will have thousands of records so I wanted the rowheader to get the values from the binded class when I set the datasource.
For example I have this class and I want the rowheaders to show the Customer ID.
private class Customer
{
public string CustomerID { get; set; }
public string CustomerFirstName { get; set; }
}
I'm afraid you have to implement datagridview's OnPaint-Method, where you paint the customer id in a loop. Once, I wanted to numerate my entries and I didn't find any other solution.