I have a binding source creditUserBindingSource in my below class which recieves a list of CreditUser class as datasource. In my DataGridView I have a DataGridViewComboBoxColumn called ResponsibleList which receives a list of string as DataSource ["Production", "Distribution", "Customer Service", "Sales"]. I want the ResponsibleList to display the responsible from the responsible variable from the list of CrteditUsers for each user.
public partial class CreditUserLimitsForm : Form
{
private List<CreditUser> creditUser;
private bool SetupCheckStatus = false;
//private Dictionary<string, string> fbu;
public CreditUserLimitsForm()
{
InitializeComponent();
}
private void CreditUserLimitsForm_Load(object sender, EventArgs e)
{
//fbu = MainForm.srv.GetFBU();
//foreach (KeyValuePair<string, string> k in fbu)
//{
// lvwFBU.Items.Add(new ListViewItem(new string[] { k.Key, k.Value }));
//}
try
{
creditUser = MainForm.srv.GetCreditUser("","").ToList();
creditUserBindingSource.DataSource = creditUser;
SetAlternateChoicesUsingDataSource(ResponsibleList);
}
catch (Exception ex)
{
Cursor = Cursors.Default;
NutraMsg.DisplayError(this, ex, MainForm.GetMessageDisplayType());
}
}
private void SetAlternateChoicesUsingDataSource(DataGridViewComboBoxColumn comboboxColumn)
{
{
comboboxColumn.DataSource = MainForm.srv.GetResponsibleList();
comboboxColumn.ValueMember = "Responsible";
comboboxColumn.DisplayMember = comboboxColumn.ValueMember;
}
}
}
Here's the code for CreditUser class
public class CreditUser : INotifyPropertyChanged
{
public string Responsible { get; set; }
public int UserId { get; set; }
public int RoutingId { get; set; }
public string UserName { get; set; }
public List<string> AllowedCustomerTypes { get; set; }
public decimal CreditLimit { get; set; }
public bool Restricted
{
get
{
foreach (UserCatalog uc in Catalogs)
{
if (uc.Restricted)
{
return true;
}
}
return false;
}
}
}
If you're binding a list of string values then don't set the DisplayMember or ValueMember. The point of those is to specify members of the items you want to use but you don't want to use members of the items. You want to use the items themselves. Here is a simple example that demonstrates this:
private class Record
{
public int Id { get; set; }
public string Name { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
var idColumn = new DataGridViewTextBoxColumn { HeaderText = "Id", DataPropertyName = "Id" };
var nameColumn = new DataGridViewComboBoxColumn
{
HeaderText = "Name",
DataPropertyName = "Name",
DataSource = new[] {"First", "Second", "Third"}
};
dataGridView1.Columns.AddRange(idColumn, nameColumn);
dataGridView1.DataSource = new BindingList<Record>
{
new() {Id = 1, Name = "First"},
new() {Id = 2, Name = "Second"},
new() {Id = 3, Name = "Third"}
};
}
I see that you have made a custom DataGridComboBoxColumn and have implemented a version of SetAlternateChoicesUsingDataSource that seems to be modeled after the method of the same name in the Microsoft code example for DataGridViewComboBoxColumn.
The purpose of SetAlternateChoicesUsingDataSource in that example is to provide ComboBox drop down options that are tailored and specified for each user using the AllowedCustomerTypes property that you show in your CreditUser class. Something like this:
But based on your comment, the choices are the same for each row. More like this:
This means that your code can be simplified.
DataSources for DataGridViewComboBoxColumn and DataGridView
I believe what might be causing the confusion is that the data sources for DataGridView and for DataGridViewComboBoxColumn are completely unrelated in this case. Since there is no need to provide each user with individualized options, the source of drop down items only needs to be set one time for the entire column.
The DataSource for DataGridViewComboBoxColumn is an array of strings named ResponsibleList that will not change.
private readonly string[] ResponsibleList = new []
{
"Production",
"Distribution",
"Customer Service",
"Sales",
String.Empty
};
The DataSource for dataGridViewCreditUser is a binding list named CreditUsers.
readonly BindingList<CreditUser> CreditUsers = new BindingList<CreditUser>();
Initialize
Assigning these data sources is done in the override of OnLoad (there's no need to have the form subscribe to its own Load event). Allow me to explain what I've done and you can modify this flow to your specific requirements.
protected override void OnLoad(EventArgs e)
{
dataGridViewCreditUser.DataSource = CreditUsers;
Adding one or more items will autogenerate the columns.
// Calls a mock method that returns a simulated response of three CreditUsers.
foreach (var creditUser in mockMainForm_srv_GetCreditUser("", ""))
{
CreditUsers.Add(creditUser);
}
Create a ComboBox column that will be swapped out for the autogenerated one. This is where ResponsibleList becomes the DataSource for the ComboBox.
var colCB = new DataGridViewComboBoxColumn
{
Name = nameof(CreditUser.Responsible),
// Connects the column value to the Responsible property of CreditUser
DataPropertyName = nameof(CreditUser.Responsible),
AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells,
// Connects the four drop-down options in ResponsibleList to the ComboBox
DataSource = ResponsibleList,
};
Perform the swap. Remove the autogenerated column and replace it with the custom version.
var index = dataGridViewCreditUser.Columns[nameof(CreditUser.Responsible)].Index;
dataGridViewCreditUser.Columns.RemoveAt(index);
dataGridViewCreditUser.Columns.Insert(index, colCB);
Make sure the cell is NOT left in an editing state after change of ComboBox or CheckBox.
dataGridViewCreditUser.CurrentCellDirtyStateChanged += (sender, e) =>
{
switch (dataGridViewCreditUser.Columns[dataGridViewCreditUser.CurrentCell.ColumnIndex].Name)
{
case nameof(CreditUser.Responsible):
case nameof(CreditUser.Restricted):
dataGridViewCreditUser.CommitEdit(DataGridViewDataErrorContexts.Commit);
break;
}
};
To monitor ongoing changes, update the Title bar whenever the source list is modified.
CreditUsers.ListChanged += (sender, e) =>
{
if ((dataGridViewCreditUser.CurrentCell != null) && (dataGridViewCreditUser.CurrentCell.RowIndex < CreditUsers.Count))
{
var creditUser = CreditUsers[dataGridViewCreditUser.CurrentCell.RowIndex];
Text = creditUser.ToString();
}
};
Now that the DataGridView is all set up the columns can be formatted.
foreach (DataGridViewColumn col in dataGridViewCreditUser.Columns)
{
if (col.Name == nameof(CreditUser.UserName))
{
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}
else
{
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
}
}
}
MOCK QUERY FOR TESTING
// MOCK for minimal example
private List<CreditUser> mockMainForm_srv_GetCreditUser(string v1, string v2)
{
return new List<CreditUser>
{
new CreditUser
{
UserName = "Tom",
CreditLimit=10000m,
},
new CreditUser
{
UserName = "Richard",
CreditLimit=1250m,
Restricted = true
},
new CreditUser
{
UserName = "Harry",
CreditLimit=10000m,
},
};
}
CreditUser class
// REDUCED for minimal example
public class CreditUser : INotifyPropertyChanged
{
string _UserName = string.Empty;
public string UserName
{
get => _UserName;
set
{
if (!Equals(_UserName, value))
{
_UserName = value;
OnPropertyChanged();
}
}
}
string _Responsible = String.Empty;
public string Responsible
{
get => _Responsible;
set
{
if (!Equals(_Responsible, value))
{
_Responsible = value;
OnPropertyChanged();
}
}
}
decimal _CreditLimit = 0;
public decimal CreditLimit
{
get => _CreditLimit;
set
{
if (!Equals(_CreditLimit, value))
{
_CreditLimit = value;
OnPropertyChanged();
}
}
}
bool _Restricted = false;
public bool Restricted
{
get => _Restricted;
set
{
if (!Equals(_Restricted, value))
{
_Restricted = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public override string ToString()
{
var ctype = Responsible == string.Empty ? "Not Specified" : $"{Responsible}";
return Restricted ?
$"{UserName} ({ctype}) Restricted" :
$"{UserName} ({ctype}) {CreditLimit}";
}
}
Related
I am trying to bind a List of a custom class to a Listbox and cannot get anything to display. The List is a subset of another List. I can bind the parent List and see the items, but not the child List. How can I get the subset List to bind to the Listbox? I have tried changing the order of the ListBox's DisplayMember, ValueMember, and DataSource properties. In debugging I can see that the DataSource has the correct values, but I can't get them to display. Relevant code below:
public class DimZone
{
public int Zone_Key { get; set; }
public int Zone_ID { get; set; }
public int Facility_Key { get; set; }
public string Zone_Name { get; set; }
public string Zone_Type { get; set; }
}
GlobalVariables Class containing global List collection:
public static List<DimZone>[] zoneCollection = new List<DimZone>[maxServerCount];
Form using global List collection and subset List:
List<DimZone> zoneCollectionAppended = new List<DimZone>();
private void StaffStatusReportForm_Load(object sender, EventArgs e)
{
facilityComboBox.DataSource = GlobalVariables.facilityCollection;
GetFacilityIndex();
CreateZoneAppendedList();
PopulateUI();
}
private void CreateZoneAppendedList()
{
foreach (var zone in GlobalVariables.zoneCollection[currentFacilityIndex])
{
if (zone.Zone_Name != "All")
{
zoneCollectionAppended.Add(zone);
}
}
}
private void PopulateUI()
{
if (zoneCollectionAppended != null)
{
zoneListBox.DisplayMember = "Zone_Name";
zoneListBox.ValueMember = "Zone_ID";
zoneListBox.DataSource = zoneCollectionAppended;
}
}
Your code contains various unclear parts. In any case, the best proceeding in these situations is setting up a properly-working simpler code and modifying it until reaching the stage you want. I can provide this properly-working first step. Sample code:
private void Form1_Load(object sender, EventArgs e)
{
List<DimZone> source = new List<DimZone>();
DimZone curZone = new DimZone() { Zone_Key = 1, Zone_ID = 11, Facility_Key = 111, Zone_Name = "1111", Zone_Type = "11111" };
source.Add(curZone);
curZone = new DimZone() { Zone_Key = 2, Zone_ID = 22, Facility_Key = 222, Zone_Name = "2222", Zone_Type = "22222" };
source.Add(curZone);
zoneListBox.DisplayMember = "Facility_Key";
zoneListBox.DataSource = source;
}
public class DimZone
{
public int Zone_Key { get; set; }
public int Zone_ID { get; set; }
public int Facility_Key { get; set; }
public string Zone_Name { get; set; }
public string Zone_Type { get; set; }
}
Try this code and confirm that the changes in zoneListBox.DisplayMember (e.g., "Zone_Key", "Zone_ID", etc.) are immediately reflected in the values being displayed by zoneListBox.
The problem was I was changing zoneListBox.DataSource from one source to another on load, which caused the error. In order for the DataSource to update properly, I had to set zoneListBox.DataSource = null before updating to a new DataSource. I don't know why I have to set it to null first, but it solved my problem. So my updated code is as follows:
private void PopulateUI()
{
if (zoneCollectionAppended != null)
{
zoneListBox.DisplayMember = "Zone_Name";
zoneListBox.ValueMember = "Zone_ID";
//DataSource had to be reset to null before updating to new DataSource
zoneListBox.DataSource = null;
zoneListBox.DataSource = zoneCollectionAppended;
}
}
So I have a page that has a TabControl that uses multiple UserControls (each user control represents the contents of a TabItem).
I have a ComboBox in both user controls that share the same ItemsSource (OrganizationSource) and SelectedValue(OrganizationSelected) properties,
I however cannot seem to bind to ComboBox within the EventDetails UserControl, but it works flawlessly inside of the OrganizationDetails UserControl.
The OutreachMVVM is set to the DataContext for the parent page that the tabs reside in. I still have to set the datacontexts for the user controls themselves for it to work properly.
I just need to figure out how to set the binding for the ComboBox inside of the EventDetails. I saw something about dependency property but I do not understand it. I thought I would be able to set the binding for the ComboBox inside of EventDetails as the same for the ComboBox inside of OrganizationDetails, but that's not the case.
internal class OutreachMVVM : ViewModelBase
{
public OutreachMVVM()
{
EventDetails = new EventDetailsVMB();
OrganizationDetails = new OrganizationDetailsVMB();
}
public EventDetailsVMB EventDetails { get; set; }
public OrganizationDetailsVMB OrganizationDetails { get; set; }
}
EventDetailsVMB:
class EventDetailsVMB : ViewModelBase
{
private string _eventTypeSelected;
private string _zipSelected;
private readonly UserListTableAdapter _userListTableAdapter = new UserListTableAdapter();
private readonly EventTypeListTableAdapter _eventTypeListTableAdapter = new EventTypeListTableAdapter();
private readonly CityListTableAdapter _cityListTableAdapter = new CityListTableAdapter();
private readonly LocationInfoByZipTableAdapter _locationInfoByZipTableAdapter = new LocationInfoByZipTableAdapter();
public string User { get; set; }
public string EventName { get; set; }
public string Location { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string County { get; set; }
public string ServiceArea { get; set; }
//Set EventType CombBox
public ObservableCollection<string> EventTypeSource
{
get
{
var eventTypeList = _eventTypeListTableAdapter.GetEventTypeList();
var eventTypeSource = new ObservableCollection<string>();
eventTypeSource.AddRange(from DataRow row in eventTypeList.Rows select row.ItemArray[0].ToString());
return eventTypeSource;
}
}
//Set User ComboBox
public ObservableCollection<string> UserSource
{
get
{
var userList = _userListTableAdapter.GetUserList();
var userSource = new ObservableCollection<string>();
foreach (var username in Enumerable.Where(userList, username => username.Username == Environment.UserName))
{
User = username.FullName;
}
userSource.AddRange(from DataRow row in userList.Rows select row.ItemArray[0].ToString());
OnPropertyChanged("User");
return userSource;
}
}
//Set City RadAutoCompleteBox
public ObservableCollection<string> CitySource
{
get
{
var cityList = _cityListTableAdapter.GetCityList();
var citySource = new ObservableCollection<string>();
citySource.AddRange(from DataRow row in cityList.Rows select row.ItemArray[0].ToString());
return citySource;
}
}
public string EventTypeSelected
{
get { return _eventTypeSelected; }
set
{
_eventTypeSelected = value;
OnPropertyChanged("EventTypeSelected");
}
}
public string ZipSelected
{
get { return _zipSelected; }
set
{
_zipSelected = value;
var locationInfo = _locationInfoByZipTableAdapter.GetLocationInfoByZip(_zipSelected);
if (locationInfo.Rows.Count != 0)
{
City = locationInfo.Rows[0].ItemArray[0].ToString();
State = locationInfo.Rows[0].ItemArray[1].ToString();
County = locationInfo.Rows[0].ItemArray[2].ToString();
ServiceArea = locationInfo.Rows[0].ItemArray[3].ToString();
}
else if (ZipSelected.Length == 5) {}
else
{
City = "";
State = "TX";
County = null;
ServiceArea = null;
}
OnPropertyChanged("City");
OnPropertyChanged("State");
OnPropertyChanged("County");
OnPropertyChanged("ServiceArea");
}
}
}
OrganizationDetailsVMB:
class OrganizationDetailsVMB : ViewModelBase
{
private string _organizationName;
private string _street1;
private string _street2;
private string _city;
private string _state;
private string _zip;
private string _county;
private string _serviceArea;
private bool _cbo;
private bool _fbo;
private bool _mo;
private bool _sbo;
private bool _sno;
private readonly OrganizationListTableAdapter _organizationListTableAdapter = new OrganizationListTableAdapter();
private readonly OrgByNameTableAdapter _orgByNameTableAdapter = new OrgByNameTableAdapter();
private readonly OrgTypeByOrgNameTableAdapter _orgTypeByOrgNameTableAdapter = new OrgTypeByOrgNameTableAdapter();
public string OrganizationSelected
{
get { return _organizationName; }
set
{
_organizationName = value;
var organizationQueryResults = _orgByNameTableAdapter.GetOrganizationByName(_organizationName);
var orgTypeQueryResults = _orgTypeByOrgNameTableAdapter.GetOrgTypeByName(_organizationName);
if (organizationQueryResults.Rows.Count != 0)
{
OrgStreet1Value = organizationQueryResults.Rows[0].ItemArray[1].ToString();
OrgStreet2Value = organizationQueryResults.Rows[0].ItemArray[2].ToString();
OrgCityValue = organizationQueryResults.Rows[0].ItemArray[3].ToString();
OrgStateValue = organizationQueryResults.Rows[0].ItemArray[4].ToString();
OrgZipValue = organizationQueryResults.Rows[0].ItemArray[5].ToString();
OrgCountyValue = organizationQueryResults.Rows[0].ItemArray[6].ToString();
OrgServiceAreaValue = organizationQueryResults.Rows[0].ItemArray[7].ToString();
CBO = Convert.ToBoolean(orgTypeQueryResults.Rows[0].ItemArray[1]);
FBO = Convert.ToBoolean(orgTypeQueryResults.Rows[0].ItemArray[2]);
SBO = Convert.ToBoolean(orgTypeQueryResults.Rows[0].ItemArray[3]);
MO = Convert.ToBoolean(orgTypeQueryResults.Rows[0].ItemArray[4]);
SNO = Convert.ToBoolean(orgTypeQueryResults.Rows[0].ItemArray[5]);
}
else
{
OrgStreet1Value = "";
OrgStreet2Value = "";
OrgCityValue = "";
OrgStateValue = "";
OrgZipValue = "";
OrgCountyValue = "";
OrgServiceAreaValue = "";
CBO = false;
FBO = false;
SBO = false;
MO = false;
SNO = false;
}
}
}
public ObservableCollection<string> OrganizationSource
{
get
{
var organizationList = _organizationListTableAdapter.GetOrganizationList();
var organizationSource = new ObservableCollection<string>();
organizationSource.AddRange(from DataRow row in organizationList.Rows select row.ItemArray[0].ToString());
return organizationSource;
}
}
public string OrgStreet1Value
{
get { return _street1; }
set
{
if (_street1 != value)
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = "OrgStreet1Value" });
_street1 = value;
OnPropertyChanged("OrgStreet1Value");
}
}
}
public string OrgStreet2Value
{
get { return _street2; }
set
{
if (_street2 != value)
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = "OrgStreet2Value" });
_street2 = value;
OnPropertyChanged("OrgStreet2Value");
}
}
}
public string OrgCityValue
{
get { return _city; }
set
{
if (_street1 != value)
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = "OrgCityValue" });
_city = value;
OnPropertyChanged("OrgCityValue");
}
}
}
public string OrgStateValue
{
get { return _state; }
set
{
if (_state != value)
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = "OrgStateValue" });
_state = value;
OnPropertyChanged("OrgStateValue");
}
}
}
public string OrgZipValue
{
get { return _zip; }
set
{
if (_zip != value)
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = "OrgZipValue" });
_zip = value;
OnPropertyChanged("OrgZipValue");
}
}
}
public string OrgCountyValue
{
get { return _county; }
set
{
if (_county != value)
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = "OrgCountyValue" });
_county = value;
OnPropertyChanged("OrgCountyValue");
}
}
}
public string OrgServiceAreaValue
{
get { return _serviceArea; }
set
{
if (_serviceArea != value)
{
Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = "OrgServiceAreaValue" });
_serviceArea = value;
OnPropertyChanged("OrgServiceAreaValue");
}
}
}
public bool CBO
{
get { return _cbo; }
set
{
_cbo = value;
OnPropertyChanged("CBO");
}
}
public bool FBO
{
get { return _fbo; }
set
{
_fbo = value;
OnPropertyChanged("FBO");
}
}
public bool SBO
{
get { return _sbo; }
set
{
_sbo = value;
OnPropertyChanged("SBO");
}
}
public bool MO
{
get { return _mo; }
set
{
_mo = value;
OnPropertyChanged("MO");
}
}
public bool SNO
{
get { return _sno; }
set
{
_sno = value;
OnPropertyChanged("SNO");
}
}
}
EventDetailsTab:
<TabItem Header="Event Details" x:Name="EventDetailsTab"
Style="{StaticResource TabStyle}"
DataContext="{Binding EventDetails}">
<eventTabs:_1_EventDetailsTab />
</TabItem>
OrganizationDetailsTab:
<TabItem Header="Organization" x:Name="OrganizationTab"
Style="{StaticResource TabStyle}"
DataContext="{Binding OrganizationDetails}">
<eventTabs:_2_OrganizationTab />
</TabItem>
As I said the bindings work flawlessly overall, but I want to reference a binding associated with OrganizationDetails for a control that resides in both the EventDetailsTab and OrganizationDetailsTab.
The code for that item is as follows...
<ComboBox Name="OrgNameComboBox" ItemsSource="{Binding OrganizationSource}"
SelectedValue="{Binding OrganizationSelected,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
You're setting the DataContext for your event details tab to {Binding EventDetails} and the DataContext for your organization tab to {Binding OrganizationDetails}, but the OrganizationSource and OrganizationSelected fields that your ComboBoxes are binding to only exist in the OrganizationDetailsVMB class. A quick hack would be to change your event details ComboBox to point to the correct place with a RelativeSource binding back to the TabItem's DataContext and down again to the OrganizationDetails:
<ComboBox Name="OrgNameComboBox" ItemsSource="{Binding OrganizationSource}"
DataContext="{Binding RelativeSource={RelativeSource AncestorType=TabControl}, Path=DataContext.OrganizationDetails}"
SelectedValue="{Binding OrganizationSelected,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
To be perfectly honest though I think you need to clean up your architecture. Your view model appears very tightly coupled to your underlying data model instead of the view, to the point where you're even accessing your table adapters in your getters which means A) you have no way of doing session management, B) your view performance will now be throttled by your DAL and you'll start having performance issues, and C) trying to introduce any type of multi-threading will cause all manner of access conflicts in your ORM.
A)
This way is good when you want have some data (like a combobox info) across an App or Multi Window:
Create 3 ViewModel (all inherited from INotifyPropertyChanged).
OrganizationViewModel: INCLUDE OrganizationSource and OrganizationSelected
OrganizationDetailsViewModel: WHITHOUT OrganizationSource and OrganizationSelected
OrganizationEventsViewModel: WHITHOUT OrganizationSource and OrganizationSelected
Now you can create a simple Static class named GeneralData & make a static property in it of OrganizationViewModel type like this:
public static class GeneralData{
public static OrganizationViewModel Organization {get;set;}
}
In the App.xaml.cs fill the Organization property of the GeneralData class with a new instance of OrganizationViewModel.
Ok... Now we have every things we need to start playing....
Every time you want to fill the ComboBox you should use the Organization [static property] of GeneralData class like this:
<ComboBox Name="OrgNameComboBox"
DataSource="{Binding Source={x:Static GeneralData.Organization}}"
ItemsSource="{Binding Source={x:Static GeneralData.Organization.OrganizationSource}}"
SelectedValue="{Binding Source={x:Static GeneralData.Organization.OrganizationSelected,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
And every time you want check which item of the Combobox is selected in code-behind (such as inside the ViewModels) you can easily check it like this:
var selectedOrganisation = GeneralData.Organization.OrganizationSelected;
Note: i recommend to you before closing the window that include the ComboBox run this code (to make sure there is not any alive reference to the static property):
BindingOperations.ClearAllBindings(OrgNameComboBox);
B)
If you have just 1 window include 2 UserControl then prevent static properties and instead of above way use this way:
Create 3 ViewModel (all inherited from INotifyPropertyChanged).
OrganizationViewModel: INCLUDE OrganizationSource and OrganizationSelected
OrganizationDetailsViewModel: WHITHOUT OrganizationSource and OrganizationSelected
OrganizationEventsViewModel: WHITHOUT OrganizationSource and OrganizationSelected
Now easily implement this scenario:
In Window1.xaml.cs:
Window1.DataSource= new OrganizationViewModel();
In EventUserControl.xaml.cs:
EventUserControl.DataSource= new OrganizationEventsViewModel();
In OrganizationDetailsUserControl.xaml.cs:
EventUserControl.DataSource= new OrganizationDetailsViewModel();
Now create a DependencyProperty in both EventUserControl and OrganizationDetailsUserControl to give them the SelectedItem of ComboBox. Your property type should be string. you can create the DependencyPropertys base on this tutorial.
For example use this name for your DependencyPropertys in both UserControls: SelectedOrganisation
OK, put the Combobox in the Window1.xaml (NOT inside any UserControl).
Now we fill these SelectedOrganisations from outer their UserControls like the following codes.
In Window1.xaml
<uc.OrganizationDetailsUserControl
SelectedOrganisation="{Binding ElementName=OrgNameComboBox, Path=SelectedItem}"/>
<uc.EventUserControl
SelectedOrganisation="{Binding ElementName=OrgNameComboBox, Path=SelectedItem}"/>
Now you have the SelectedItem of the ComboBox inside your UserControl you can play with it inside the UserControls and call some methods of the ViewModels with SelectedOrganisation as method parameter.
The route I took is the MVVM Light Toolkit.
I have a custom dataset style class defined as:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace LibrarySort.Data
{
public class LibraryData
{
private AlbumList albums;
public AlbumList Albums { get { return albums; } }
public LibraryData()
{
this.albums = new AlbumList();
this.albums.AllowEdit = true;
this.Albums.AllowNew = true;
this.Albums.AllowRemove = true;
}
public void FillAll()
{
this.Albums.Fill();
}
}
public class AlbumList : BindingList<Album>
{
public AlbumList()
{
}
public void Fill()
{
int id = 1;
Album album1 = new Album();
album1.Id = id++;
album1.Artist = "Classical Piano Artist";
album1.Download = true;
album1.Person = null;
album1.Price = (decimal?)3.49;
album1.Tags.Add("classical");
album1.Tags.Add("piano");
album1.Title = "Classical Piano";
Album album2 = new Album();
album2.Id = id++;
album2.Artist = "Thrash Metal Artist";
album2.Download = false;
album2.Person = null;
album2.Price = (decimal?)7.99;
album2.Tags.Add("thrash metal");
album2.Title = "Thrash Metal";
this.Items.Add(album1);
this.Items.Add(album2);
}
}
}
I also a have Form object that inside a TabControl has a DataGridView. In the designer, I created a BindingSource and used the Add Project Data Source to create a source from the top level LibraryData object. I then bind the DataGridView to the "Albums" data member, all in the designer, and the columns get populated in the designer as expected.
When running the code, the table isn't populated, which makes sense as the Fill() hasn't been run. So I create a Load event handler for the form as follows:
private void MainForm_Load(object sender, EventArgs e)
{
LibraryData data = (LibraryData)bindingSource.DataSource;
data.FillAll();
}
However on run I get the following in MainForm_Load():
System.InvalidCastException was unhandled
Message="Unable to cast object of type 'System.RuntimeType' to type 'LibrarySort.Data.LibraryData'
I've Googled extensively on this, and in StackOverflow but no luck. Am I missing something?
Update: DataSource is definitely non-null. Also interestingly, in the designer code I see this:
this.bindingSource.DataSource = typeof(LibrarySort.Data.LibraryData);
Update 2: Album class and parent Item:
namespace LibrarySort.Data
{
public class Album : Item
{
bool download = false;
public bool Download { get { return download; } set { download = value; } }
string artist = null;
public string Artist { get { return artist; } set { artist = value; } }
}
}
namespace LibrarySort.Data
{
public class Item
{
int id = -1;
public int Id { get { return id; } set { id = value; } }
// FK to Person that currently has possession of item
int? person = null;
public int? Person { get { return person; } set { person = value; } }
string title = null;
public string Title { get { return title; } set { title = value; } }
decimal? price = null;
public decimal? Price { get { return price; } set { price = value; } }
State state = State.Owned;
public State State { get { return state; } set { state = value; } }
List<string> tags = null;
public List<string> Tags
{
get
{
if (tags == null)
tags = new List<string>();
return tags;
}
// No set needed
}
}
}
I think the issue is that although you have a binding source on your form, you have not set the datasource of the binding source.
Assuming that bindingSource is the datasource you dropped on your form, then try the following in the MainForm_Load:
LibraryData data = new LibraryData();
data.FillAll();
bindingSource.DataSource = data;
I have a BindingList with my class where I would like to populate a ComboBox using a property of it so when my list changes the ComboBox would change as well.
public class UserAccess
{
public override string ToString()
{
return Access;
}
public int AccessId { get; set; }
public string Access { get; set; }
public List<string> Command = new List<string>();
public bool HasCommand(string cmd)
{
return this.Command.Any(x => x == cmd);
}
}
public BindingList<UserAccess> accessList = new BindingList<UserAccess>();
On my form load I assign it to the ComboBox:
myComboBox.DataSource = accessList;
I want to populate the box with Access or with the AccessId as value and Access as the printed name.
Problem is that it will print only the last item of the list to the combobox what am I doing wrong ?
Use DisplayMember to specify what field to use for display in the ComboBox.
Make accessList readonly to guarantee that you never recreate a new instance of the list. If you don't make it readonly, this may introduce a subtle bug, if you don't reassign DataSource whenever you recereate accessList.
private readonly BindingList<UserAccess> accessList = new BindingList<UserAccess>();
public Form1()
{
InitializeComponent();
comboBox1.ValueMember = "AccessId";
comboBox1.DisplayMember = "Access";
comboBox1.DataSource = accessList;
}
private void button1_Click(object sender, EventArgs e)
{
accessList.Add(new UserAccess { AccessId = 1, Access = "Test1" });
accessList.Add(new UserAccess { AccessId = 2, Access = "Test2" });
}
If you need to be able to change items properties in accessList (like accessList[0].Access = "Test3") and see the changes reflected in UI, you need to implement INotifyPropertyChanged.
For example:
public class UserAccess : INotifyPropertyChanged
{
public int AccessId { get; set; }
private string access;
public string Access
{
get
{
return access;
}
set
{
access = value;
RaisePropertyChanged("Access");
}
}
private void RaisePropertyChanged(string propertyName)
{
var temp = PropertyChanged;
if (temp != null)
temp(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Below is the class file
public class BillDetails
{
private string chargecategory;
[XmlAttribute("ChargeCategory")]
public string ChargeCategory
{
get { return chargecategory; }
set { chargecategory = value; }
}
private string customername;
[XmlAttribute("CustomerName")]
public string CustomerName
{
get { return customername; }
set { customername = value; }
}
private List<Details> details;
[XmlArray("Details")]
[XmlArrayItem("details")]
// public List<Details> details = new List<Details>();
public List<Details> Details
{
get { return details; }
set { details = value; }
}
now in my code I need to databind only the properties which belong to List
List<BillDetails> billlist = new List<BillDetails>();
public int x;
List<Details> newdetails = new List<Details>();
public void Button1_Click(object sender, EventArgs e)
{
if (IsValidPost())
{
if (Session["BillList"] == null)
{
newdetails.Add(new Details() { ChargeCode = ChargeCode.Text, MaterialCode = MaterialCode.Text, GLAccount = GLAccount.Text, CostCenter = CostCenter.Text, Price = Convert.ToDecimal(Price.Text), Quantity = Convert.ToInt32(Quantity.Text), UOM = UOM.Text, Total = Convert.ToDecimal(Price.Text) * Convert.ToInt32(Quantity.Text) });
billlist.Add(new BillDetails() { ChargeCategory = ChargeCategory.Text, Details = newdetails.ToList(), CustomerName = CustomerName.Text });
GridView1.DataSource = newdetails *---works ...but if I give the datasource as billlist it does not ...but I want get down to newdetails from billlist.
GridView1.DataBind();
//Session["BillList"] = newdetails;
Session["BillList"] = billlist;
cleartextboxes();
serializetoxml(billlist);
}
how do i achieve this...also in the ascx file how do I databind the columns to the properties which are in details
I would assume DataMember = "Details"; would do this (but I don't personally use webforms data-binding, so my apologies if this fails).