WPF Multiple ViewModels and Multiple UserCotnrols - c#

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.

Related

MVVM WPF ViewModel DispatcherTimer not updating view?

I have a DispatcherTimer in my ViewModel that i can see firing every interval, but the view is not being updated?
the feed data comes from a xml url and i am trying to refresh the form every x seconds. Maybe more or less lables / differnt status
heres the code snippets:
ViewModel.cs
public class Nodes
{
public string name { get; set; }
public string id { get; set; }
public string status { get; set; }
public string last { get; set; }
public int level { get; set; }
public string parent { get; set; }
}
public ObservableCollection<CI> CIs
{
get;
set;
}
DispatcherTimer LogTimer;
public void LoadCIs()
{
ObservableCollection<CI> cis = new ObservableCollection<CI>();
LogTimer = new DispatcherTimer();
LogTimer.Interval = TimeSpan.FromMilliseconds(10000);
LogTimer.Tick += (s, e) =>
{
//pull node list
List<Nodes> SortedList = PopulateNodes();
foreach (Nodes Node in SortedList)
{
//create labels for all top level nodes
if (Node.level == 3)
{
cis.Add(new CI { NodeName = Node.name, NodeStatus = Node.status });
}
}
CIs = cis;
};
LogTimer.Start();
}
Model.cs
public class CI : INotifyPropertyChanged {
private string nodeName;
private string nodeStatus;
public string NodeName {
get {
return nodeName;
}
set {
if (nodeName != value) {
nodeName = value;
RaisePropertyChanged("NodeName");
}
}
}
public string NodeStatus
{
get
{
return nodeStatus;
}
set
{
if (nodeStatus != value)
{
nodeStatus = value;
RaisePropertyChanged("NodeStatus");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
view.xaml
<Grid>
<ItemsControl ItemsSource = "{Binding Path = CIs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Label Content = "{Binding Path = NodeName, Mode = OneWay}"
Background = "{Binding Path = NodeStatus, Mode = OneWay}"
Foreground="White"
FontFamily="Arial Black"
HorizontalContentAlignment="Center"
BorderBrush="Black"
BorderThickness="1,1,1,1"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
how the form should look without timer enabled / commented out:
with the timer code enabled nothing is added to grid:
Thanks for looking
The problem:
You are changing the Collection CIs but do not notify it is changed. ObservableCollections report their changes but you're overwriting it, it will not report that.
Option 1:
Because you use an ObservableCollection you can add it to the bound collection directly and it will notify the UI automatically.
So instead of:
cis.Add(new CI { NodeName = Node.name, NodeStatus = Node.status });
Do this:
CIs.Add(new CI { NodeName = Node.name, NodeStatus = Node.status });
if you do this you have to initialize CIs first:
public ObservableCollection<CI> CIs
{
get;
set;
} = new ObservableCollection<CI>(); // < initialize it
Option 2:
Add the INotifyPropertyChanged interface to the Nodes class and notify like this:
this.PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( nameof( this.CIs ) ) );
in the setter of CIs

Bind individual elements of collection in WPF application

It is WPF application and I’m trying to bind individual collection item property in TextBlock. I search on StackOverflow and many others have asked similar questions and they have their solution working. I tried to access value same way but somehow, it’s not displaying Index value in my case so posting similar question. Please help me to identify what I'm doing wrong here.
View model
public class SequeanceViewModel
{
public ObservableCollection<Sequence> SequenceList = new ObservableCollection<ViewModel.Sequence>();
public SequeanceViewModel()
{
for (int i = 1; i <= 6; i++)
{
SequenceList.Add(new ViewModel.Sequence() { Index = i, Name = "Name goes here" });
}
}
}
public class Sequence : INotifyPropertyChanged
{
private int index { get; set; }
private bool current { get; set; }
private string name;
public int Index
{
get
{
return index;
}
set
{
index = value;
OnPropertyChanged(new PropertyChangedEventArgs("Index"));
}
}
public bool Current
{
get
{
return current;
}
set
{
current = value;
OnPropertyChanged(new PropertyChangedEventArgs("Current"));
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Window code
SequeanceViewModel sequeanceViewModel;
public Validation()
{
InitializeComponent();
sequeanceViewModel = new SequeanceViewModel();
this.DataContext = sequeanceViewModel;
}
Binding in xaml
<TextBlock Text="{Binding SequenceList[0].Index, Mode=OneWay}"></TextBlock>
Since you can only bind to public properties, you must define SequenceList as a property and not as a public field:
public ObservableCollection<Sequence> SequenceList { get; } = new ObservableCollection<ViewModel.Sequence>();
You must expose SequenceList as a property instead of a public variable. Otherwise you cannot bind to it.

Filtering a WPF Datagrid with a Textbox

I'm making this application that pulls data from a Sharepoint site using XML Node,
private XmlNode GetListItems(string listTitle)
{
var client = new Bluejeanware.MWELS.Lists();
System.Net.NetworkCredential passCredentials = new System.Net.NetworkCredential("username", "password", "domain");
client.Credentials = passCredentials;
return client.GetListItems(listTitle, string.Empty, null, null, string.Empty, null, null);
}
public void BindSPDataSource()
{
var data = GetListItems("Tasks");
var result = XElement.Parse(data.OuterXml);
XNamespace z = "#RowsetSchema";
var taskItems = from r in result.Descendants(z + "row")
select new
{
TaskName = r.Attribute("ows_LinkTitle").Value,
DueDate = r.Attribute("ows_DueDate") != null ? r.Attribute("ows_DueDate").Value : string.Empty,
AssignedTo = r.Attribute("ows_AssignedTo") != null ? r.Attribute("ows_AssignedTo").Value : string.Empty,
};
dataGridView.ItemsSource = taskItems;
}
I'd like to filter the data being pulled with a Textbox, a good example of what it should be is this Stackoverflow post
I'm having a hard time on transitioning this code into a way that it would work with the way my application is adding the data to my Datagrid, any ideas?
The following example demonstrates how to:
retrieve list data from SharePoint via SharePoint Web Services
bind to DataGrid and enable filtering
XAML:
<Window x:Class="SPO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Tasks" Width="800px" Height="600px" Name="TasksWindow">
<StackPanel DataContext="{Binding ElementName=TasksWindow}">
<TextBox Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}" />
<DataGrid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding ListItemCollection}" />
</StackPanel>
</Window>
Code behind:
namespace SPO
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
ListItemCollection = CollectionViewSource.GetDefaultView(LoadTasks());
ListItemCollection.Filter = FilterTask;
}
public bool FilterTask(object value)
{
var entry = value as TaskEntry;
if (entry != null)
{
if (!string.IsNullOrEmpty(_filterString))
{
return entry.TaskName.Contains(_filterString);
}
return true;
}
return false;
}
/// <summary>
/// Bind SP Data Source
/// </summary>
private IEnumerable<TaskEntry> LoadTasks()
{
var data = GetListItems("http://intranet.contoso.com","Tasks");
var result = XElement.Parse(data.OuterXml);
XNamespace z = "#RowsetSchema";
var taskItems = from r in result.Descendants(z + "row")
select new TaskEntry
{
TaskName = r.Attribute("ows_LinkTitle").Value,
DueDate = r.Attribute("ows_DueDate") != null ? r.Attribute("ows_DueDate").Value : string.Empty,
AssignedTo = r.Attribute("ows_AssignedTo") != null ? r.Attribute("ows_AssignedTo").Value : string.Empty,
};
return taskItems;
}
private XmlNode GetListItems(string webUri,string listTitle)
{
var client = new Lists.Lists();
client.Url = webUri + "/_vti_bin/Lists.asmx";
return client.GetListItems(listTitle, string.Empty, null, null, string.Empty, null, null);
}
public ICollectionView ListItemCollection
{
get { return _listItemCollection; }
set { _listItemCollection = value; NotifyPropertyChanged("ListItemCollection"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public string FilterString
{
get { return _filterString; }
set
{
_filterString = value;
NotifyPropertyChanged("FilterString");
if (_listItemCollection != null)
{
_listItemCollection.Refresh();
}
}
}
private ICollectionView _listItemCollection;
private string _filterString;
}
public class TaskEntry
{
public string TaskName { get; set; }
public string DueDate { get; set; }
public string AssignedTo { get; set; }
}
}
Result
-First define a TaskItem class to hold the properties of each node in your OuterXml.
-Make sure your ViewModel (or your codebehind if the DataContext is set to it) implements the INorifyPropertyChanged Interface to propagate the changes in the properties to the UI).
-Then turn your taskItems into an ObservableCollection Property of the TaskItem class, and define the filter property as well which is bond to the filter TextBox.
here is the full code
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
//Populate the TaskItems collection using BindSPDataSource() method
}
private String _filter = String.Empty;
public String Filter
{
get
{
return _filter;
}
set
{
if (_filter == value)
{
return;
}
_filter = value;
OnPropertyChanged();
TaskItems = new ObservableCollection<TaskItem>(TaskItems.Where(x => x.AssignedTo.ToLower().Contains(_filter) ||
x.DueDate.ToLower().Contains(_filter) ||
x.TaskName.ToLower().Contains(_filter)
));
}
}
private ObservableCollection<TaskItem> _taskItem;
public ObservableCollection<TaskItem> TaskItems
{
get
{
return _taskItem;
}
set
{
if (_taskItem == value)
{
return;
}
_taskItem = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class TaskItem
{
public String TaskName { get; set; }
public String DueDate { get; set; }
public String AssignedTo { get; set; }
}
and the Xaml:
<StackPanel>
<TextBox Text="{Binding Filter,Mode=TwoWay}" HorizontalAlignment="Stretch"/>
<DataGrid ItemsSource="{Binding TaskItems}" AutoGenerateColumns="True">
</DataGrid>
</StackPanel>
and don't forget to set the DataContext :
DataContext="{Binding RelativeSource={RelativeSource Self}}"
and Finally in the constructor don't populate the DataGrid ItemSource Directly, change this in the BindSPDataSource() method :
dataGridView.ItemsSource = taskItems;
to something like this :
TaskItems=new ObservableCollection(taskItems);

How to Bind ObservableCollection Class to DataGrin in WPF

All, I am creating a data set which is 'bound' to a DataGrid at run-time. I pull in some data that is used to build a class which is inheriting from ObservableCollection<T>. The class in question is
public class ResourceData : ObservableCollection<ResourceRow>
{
public ResourceData(List<ResourceRow> resourceRowList) : base()
{
foreach (ResourceRow row in resourceRowList)
Add(row);
}
}
public class ResourceRow
{
private string keyIndex;
private string fileName;
private string resourceName;
private List<string> resourceStringList;
public string KeyIndex
{
get { return keyIndex; }
set { keyIndex = value; }
}
public string FileName
{
get { return fileName; }
set { fileName = value; }
}
public string ResourceName
{
get { return resourceName; }
set { resourceName = value; }
}
public List<string> ResourceStringList
{
get { return resourceStringList; }
set { resourceStringList = value; }
}
}
I return a ResourceData object from a method called BuildDataGrid(), defined as
private ResourceData BuildDataGrid(Dictionary<string, string> cultureDict)
{
// ...
}
I then set the DataGrid's ItemSource to this ResourceData object via
dataGrid.ItemSource = BuiltDataGrid(cultureDictionary);
however, this is not correctly expanding the ResourceStringList within ResourceRow. I get displayed:
My question is: How can I amend my ResourceRow class to allow the DataGrid to automatically read and display the contents of a ResourceData object? I want to display each item in the ResourceStringList in a separate column.
Thanks for your time.
Here's my solution - I changed up the control, rather than the ResourceRow but I think it achieves what you're looking for.
We just create a DataGrid with design-time columns in the xaml, and then add the run-time columns dynamically in the control's constructor. A couple things to keep in mind - we're assuming the first row of the ResourceData is a good model for all of the rows since this is the one we use to determine the columns to add to the datagrid in the constructor. Second, Since ResourceRow does not implement INotifyPropertyChanged, we won't get updated values for the columns that come from the ResourceStringList - but that can easily be changed.
The Code-behind:
namespace WpfApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ResourceData data = GetData();
_dataGrid.ItemsSource = data;
for (int i = 0; i < data[0].ResourceStringList.Count; i++)
{
DataGridTextColumn column = new DataGridTextColumn();
column.Binding = new Binding(string.Format("ResourceStringList[{0}]", i));
column.Header = string.Format("dynamic column {0}", i);
_dataGrid.Columns.Add(column);
}
}
public ResourceData GetData()
{
List<ResourceRow> rows = new List<ResourceRow>();
for (int i = 0; i < 5; i++)
{
rows.Add(new ResourceRow() { KeyIndex = i.ToString(), FileName = string.Format("File {0}", i), ResourceName = string.Format("Name {0}", i), ResourceStringList = new List<string>() { "first", "second", "third" } });
}
return new ResourceData(rows);
}
}
public class ResourceData : ObservableCollection<ResourceRow>
{
public ResourceData(List<ResourceRow> resourceRowList)
: base()
{
foreach (ResourceRow row in resourceRowList)
Add(row);
}
}
public class ResourceRow
{
private string keyIndex;
private string fileName;
private string resourceName;
private List<string> resourceStringList;
public string KeyIndex
{
get { return keyIndex; }
set { keyIndex = value; }
}
public string FileName
{
get { return fileName; }
set { fileName = value; }
}
public string ResourceName
{
get { return resourceName; }
set { resourceName = value; }
}
public List<string> ResourceStringList
{
get { return resourceStringList; }
set { resourceStringList = value; }
}
}
}
The xaml:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="_dataGrid" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding KeyIndex}" Header="Key Index"/>
<DataGridTextColumn Binding="{Binding FileName}" Header="File Name"/>
<DataGridTextColumn Binding="{Binding ResourceName}" Header="Resource Name"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Your properties need to inherit from INotifyPropertyChanged, so they can update the UI, and so does your ViewModel too. Have your view model (or gasp! code behind) inherit from INotifyPropertyChanged. Implement the interface then you can do this for your properties.
private string _keyIndex= string.Empty;
public string KeyIndex
{
get { return this._keyIndex; }
set
{
this._keyIndex= value;
this.RaisePropertyChangedEvent("KeyIndex");
}
}
#Andrew's answer is correct, but I like simplicity and ease of use so I implemented a way for the columns to be automatically set up and bound to the datagrid with the names I wanted without have to use the datagrid columns.
Declare a column name class that inherits from attribute like so:
public class ColumnNameAttribute : Attribute
{
public ColumnNameAttribute(string Name) { this.Name = Name; }
public string Name { get; set; }
}
Decorate your model with the Column Name like so:
public class Data
{
[ColumnName("Name")]
public string Name{ get; set; }
[ColumnName("Customer")]
public string Client { get; set; }
[ColumnName("Activity Type")]
public string Activity_Type { get; set; }
}
Set AutoGenerateColumns to True and then add this event handler:
private void gen_AutoGridColumns(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var desc = e.PropertyDescriptor as PropertyDescriptor;
if (desc.Attributes[typeof(ColumnNameAttribute)] is ColumnNameAttribute att) e.Column.Header = att.Name;
//set the width for each column
e.Column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
}
You now can skip adding the DataGridColumns definitions into the DataGrid and still have them named whatever you want.

TextBox will not display the contents of a bound static generic list item

In WPF, I have a static "Customer" object in the definition for MainWindow.xaml.cs. This customer has a publicly accessible string Name property. In expression blend 4, I click on the "Advanced Options" box beside the "Text" property for the TextBox that I would like to bind to the customer name. I then click "Data Binding..." -> "Element Property" -> "window" under "scene elements" -> click the expander arrow beside "ActiveCustomer" -> then click "Name" -> "OK". The binding is to a readonly TextBox, so One Way binding is acceptable as the default. But when I run my app, it doesn't display the customer's name. Any Suggestions?
<TextBox x:Name="AIAHNameTextBox" Height="26" Canvas.Left="90" TextWrapping="Wrap" Canvas.Top="8" Width="100" IsReadOnly="True" VerticalContentAlignment="Center" Text="{Binding ActiveCustomer.Name, ElementName=window, Mode=OneWay}" />
ActiveCustomer is an instance of my Customer class:
namespace WPFBankingSystem
{
public enum CustomerStatus
{ Open, Closed }
public enum TransferType
{ CheckingToSaving, SavingToChecking }
[Serializable]
public class Customer
{
private string address;
private Checking chkAcc;
private string name;
private int pin;
private Saving savAcc;
private string ssn;
private AccountStatus status;
private string tel;
public string Address
{
get { return address; }
set { address = value; }
}
public Checking ChkAcc
{
get { return chkAcc; }
set { chkAcc = value; }
}
public string Name
{ get { return name; } }
public int Pin
{
get { return pin; }
set { pin = value; }
}
public Saving SavAcc
{
get { return savAcc; }
set { savAcc = value; }
}
public string Ssn
{ get { return ssn; } }
public AccountStatus Status
{
get { return status; }
set { status = value; }
}
public string Tel
{
get { return tel; }
set { tel = value; }
}
public void create(string Name, string Address, string TelephoneNumber, string SSN, int PIN)
{
this.address = Address;
this.name = Name;
this.pin = PIN;
this.ssn = SSN;
this.status = AccountStatus.Open;
this.tel = TelephoneNumber;
}
public void delete()
{
if (this.chkAcc != null)
{ chkAcc.close(); }
if (this.savAcc != null)
{ savAcc.close(); }
}
public bool hasChkAcc()
{ return (this.chkAcc != null) ? true : false; }
public bool hasSavAcc()
{ return (this.savAcc != null) ? true : false; }
public void show()
{ }
public void transfer(double Amount, TransferType Type)
{
if(this.hasChkAcc() && this.hasSavAcc())
{
switch(Type)
{
case TransferType.CheckingToSaving:
this.chkAcc.Balance -= Amount;
this.savAcc.Balance += Amount;
break;
case TransferType.SavingToChecking:
this.savAcc.Balance -= Amount;
this.chkAcc.Balance += Amount;
break;
}
}
else
{ throw new Exception("You do not have both a checking account and a saving account."); }
}
public Customer()
{ }
~Customer()
{ this.delete(); }
}
}
Inside MainWindow.xaml.cs, the customer is defined just as a public Customer object:
public Customer ActiveCustomer
{ get; set; }
You cannot bind to static properties like you can to instance properties. The property ActiveCustomer then does not exist on the element named window it exists in the class MainWindow. You should be able to fix the binding by using a Source in conjunction with x:Static:
{Binding Name, Source={x:Static local:MainWindow.ActiveCustomer}}
Note that x:Static has a very specific syntax, it does not allow an arbitrary path or the like.
Even if the binding works this is problematic though as you cannot implement INPC for static properties, so if you assign a new object to ActiveCustomer there will be no update.

Categories

Resources