MVVM WPF ViewModel DispatcherTimer not updating view? - c#

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

Related

Populating a WPF ListBox based on a ComboBox Selection

I am trying to populate a WPF ListBox with data from a SQL Stored Procedure based on a ComboBox Selection. I've gotten the ComboBox to work like its supposed to, but I can't get the ListBox to display any data. My naming might be a little weird, but think of it as: the ComboBox gets all Recipes from SQL and the ListBox needs to display a list of Ingredients and their Amounts based on the users selection from that ComboBox. The API and Stored Procedures(...GetAll() for the ComboBox and GetByRationId() for the ListBox...) work, as I can retrieve the correct data using Swagger in the API and I can Populate the ComboBox and the RationId TextBlock in the UI, but I can't get the ListBox to show any data. I am still new to programming and I'm following tutorials etc. and I can't seem to find anything that speaks to my case specifically. I'm guessing I'm missing something. I've added the aforementioned TextBlock just to display the RationId, which is what needs to be used to get the correct data from SQL, as a test, just to make sure that the Id was getting through...and it is.
Here's the Xaml...
<StackPanel Grid.Column="1" Margin="50" Orientation="Vertical">
<ComboBox x:Name="FeedGroup" MinWidth="300" MinHeight="50"
SelectedItem="{Binding SelectedFeedGroup}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FeedGroupName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock x:Name="SelectedFeedGroup_RationId" Height="81"/>
<ListBox x:Name="FeedGroupRation" MinHeight="200" Padding="20" ItemsSource="{Binding SelectedFeedGroupRation}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="10" HorizontalAlignment="Center">
<TextBlock Text="{Binding CommodityName}" FontSize="20" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding CommodityPercentage}" FontSize="16" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Here is the ViewModel Class...
public class FeedGroupPageViewModel : Screen
{
IFeedGroupEndPoint _feedGroupEndPoint;
IFeedGroupRationEndPoint _feedGroupRationEndPoint;
IMapper _mapper;
private readonly StatusInfoViewModel _status;
private readonly IWindowManager _window;
public FeedGroupPageViewModel(IFeedGroupEndPoint feedGroupEndPoint,
IFeedGroupRationEndPoint feedGroupRationEndpoint,
IConfigHelper configHelper,
IMapper mapper,
StatusInfoViewModel status,
IWindowManager window)
{
_feedGroupEndPoint = feedGroupEndPoint;
_feedGroupRationEndPoint = feedGroupRationEndpoint;
_configHelper = configHelper;
_mapper = mapper;
_status = status;
_window = window;
}
protected override async void OnViewLoaded(object view)
{
base.OnViewLoaded(view);
try
{
await LoadFeedGroup();
}
catch (Exception ex)
{
}
}
private async Task LoadFeedGroup()
{
var FeedGroupList = await _feedGroupEndPoint.GetAll();
var feedGroup = _mapper.Map<List<FeedGroupDisplayModel>>(FeedGroupList);
FeedGroup = new BindableCollection<FeedGroupDisplayModel>(feedGroup);
}
private BindableCollection<FeedGroupDisplayModel> _feedGroup;
public BindableCollection<FeedGroupDisplayModel> FeedGroup
{
get { return _feedGroup; }
set
{
_feedGroup = value;
NotifyOfPropertyChange(() => FeedGroup);
}
}
private FeedGroupDisplayModel _selectedFeedGroup;
public FeedGroupDisplayModel SelectedFeedGroup
{
get { return _selectedFeedGroup; }
set
{
_selectedFeedGroup = value;
NotifyOfPropertyChange(() => SelectedFeedGroup);
}
}
private BindableCollection<FeedGroupRationModel> _feedGroupRation;
public BindableCollection<FeedGroupRationModel> FeedGroupRation
{
get { return _feedGroupRation; }
set
{
_feedGroupRation = value;
NotifyOfPropertyChange(() => FeedGroupRation);
}
}
private BindableCollection<FeedGroupRationModel> _selectedFeedGroupRation;
public BindableCollection<FeedGroupRationModel> SelectedFeedGroupRation
{
get { return _selectedFeedGroupRation; }
set
{
_selectedFeedGroupRation = value;
NotifyOfPropertyChange(() => SelectedFeedGroupRation);
}
}
}
And here are the Model Classes
public class FeedGroupDisplayModel : INotifyPropertyChanged
{
public int Id { get; set; }
public string UserId { get; set; }
public string FeedGroupName { get; set; }
public DateTime CreateDate { get; set; }
public DateTime LastModified { get; set; }
public int RationId { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void CallPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class FeedGroupRationModel : INotifyPropertyChanged
{
public int Id { get; set; }
public string UserId { get; set; }
public int RationId { get; set; }
public string RationName { get; set; }
public int CommodityId { get; set; }
public string CommodityName { get; set; }
public int CommodityPercentage { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void CallPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And Here are My Endpoint Classes
public class FeedGroupEndPoint : IFeedGroupEndPoint
{
private IAPIHelper _apiHelper;
public FeedGroupEndPoint(IAPIHelper apiHelper)
{
_apiHelper = apiHelper;
}
public async Task<List<FeedGroupModel>> GetAll()
{
using (HttpResponseMessage response = await _apiHelper.ApiClient.GetAsync("/api/FeedGroup"))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<List<FeedGroupModel>>();
return result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
and
public class FeedGroupRationEndPoint : IFeedGroupRationEndPoint
{
private IAPIHelper _apiHelper;
public FeedGroupRationEndPoint(IAPIHelper apiHelper)
{
_apiHelper = apiHelper;
}
public async Task<List<FeedGroupRationModel>> GetRationById()
{
using (HttpResponseMessage response = await _apiHelper.ApiClient.GetAsync("/api/FeedGroup"))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<List<FeedGroupRationModel>>();
return result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
I can add more info if needed. I've been working on this for quite awhile now and I'm just out of ideas. Any help would be greatly appreciated!
Thanks in advance!!
You don't seem to set the FeedGroupRation that the ListBox binds to somewhere.
I guess you want to fetch the items and set the property when the SelectedFeedGroup property is set. You could then hook up an event handler to the PropertyChanged event or override the NotifyOfPropertyChange method. Something like this:
public override async void NotifyOfPropertyChange([CallerMemberName] string propertyName = null)
{
base.NotifyOfPropertyChange(propertyName);
if (propertyName == nameof(FeedGroup))
{
//get the items...
var results = await ...;
//set the source property
FeedGroupRation = results;
}
}
As #Michal Davis comment stated I was missing a method for loading the ration, so I added LoadFeedGroupRation()...
private async Task LoadFeedGroupRation()
{
var _feedGroupRation = await _feedGroupRationEndPoint.GetRation();
var feedGroupRation = _mapper.Map<List<FeedGroupRationDisplayModel>>
(_feedGroupPenList);
FeedGroupRationList = new BindableCollection<FeedGroupRationDisplayModel>
(feedGroupRation);
}
Also based on #EldHasp's comment I updated the SelectedFeedGroup setter...
public FeedGroupDisplayModel SelectedFeedGroup
{
get { return _selectedFeedGroup; }
set
{
_selectedFeedGroup = value;
var FeedGroupRation = LoadFeedGroup
NotifyOfPropertyChange(() => SelectedFeedGroup);
}
}
I Don't know if this was the best way but I worked for my case.

Wpf - Binding one gridcontrol selected row data and populate it in another gridcontrol

I have one gridcontrol with various fields which i havent mentioned in my code
<dxg:GridControl HorizontalAlignment="Stretch" Height="300" VerticalAlignment="Top" x:Name="grid1" AutoPopulateColumns="False" ItemsSource="{Binding Collection1}" >
<dxg:GridControl.View >
<dxg:TableView x:Name="TableView1" />
</dxg:GridControl.View>
.
.
.
.
I have another grid control on the same page with various fields
<dxg:GridControl HorizontalAlignment="Stretch" Height="250" VerticalAlignment="Top" x:Name="grid2" AutoPopulateColumns="False"
ItemsSource="{Binding ElementName="TableView1" ,path=Collection2.FocusedRow}" >
<dxg:GridControl.View >
<dxg:TableView x:Name="TableView2" />
</dxg:GridControl.View>
.
.
.
.
now collection1 Id is primary key and collection2 colID is foreign key both are having relationship with each other
Scenario here is if i select a row in grid1 all the corresponding records must be displayed in grid 2
public class myCollection: BindingList<orders>
{
public DataContext dc;
public myCollection(IList<orders> list)
: base(list)
{
}
protected override void RemoveItem(int index)
{
orders deleteItem = this.Items[index];
if (Dc.Order != null)
{
Dc.Order.DeleteOnSubmit(deleteItem);
}
base.RemoveItem(index);
}
}
My generic class for orders and generic class for master is the same
If I speak in terms of XAML properties, here you want to update ItemsSource property of 2nd Datagrid on basis of SelectedItem property of 1st Datagrid.
To achieve this, add a new property "SelectedItemDg1" in ViewModel which will hold the selection of 1st DataGrid. In Setter of this "SelectedItemDg1" property, set Collection2 as per your need.
Make sure to implement INotifyPropertyChanged interface and use ObservableCollection type for both the collections.
Following is the code sample for same :
Model Classes:
public class Country
{
public string CountryName { get; set; }
public int CountryId { get; set; }
public List<State> States { get; set; }
}
public class State
{
public string StateName { get; set; }
public int StateId { get; set; }
}
ViewModel :
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
CountriesCollection = new ObservableCollection<Country>();
StateCollection = new ObservableCollection<State>();
LoadData();
}
private ObservableCollection<Country> _CountriesCollection;
public ObservableCollection<Country> CountriesCollection
{
get { return _CountriesCollection; }
set
{
_CountriesCollection = value;
NotifyPropertyChanged("CountriesCollection");
}
}
private ObservableCollection<State> _StatesCollection;
public ObservableCollection<State> StateCollection
{
get { return _StatesCollection; }
set
{
_StatesCollection = value;
NotifyPropertyChanged("StateCollection");
}
}
private Country _SelectedCountry;
public Country SelectedCountry
{
get { return _SelectedCountry; }
set
{
_SelectedCountry = value;
if (_SelectedCountry != null && _SelectedCountry.States != null)
{
StateCollection = new ObservableCollection<State>(_SelectedCountry.States);
}
NotifyPropertyChanged("SelectedCountry");
}
}
private void LoadData()
{
if (CountriesCollection != null)
{
CountriesCollection.Add(new Country
{
CountryId = 1,
CountryName = "India",
States = new List<State>
{
new State { StateId = 1, StateName = "Gujarat"},
new State { StateId = 2, StateName = "Punjab"},
new State { StateId = 3, StateName = "Maharastra"}
}
});
CountriesCollection.Add(new Country
{
CountryId = 2,
CountryName = "Chine",
States = new List<State>
{
new State { StateId = 4, StateName = "Chine_State1"},
new State { StateId = 5, StateName = "Chine_State2"},
new State { StateId = 6, StateName = "Chine_State3"}
}
});
CountriesCollection.Add(new Country
{
CountryId = 3,
CountryName = "japan",
States = new List<State>
{
new State { StateId = 7, StateName = "Japan_State1"},
new State { StateId = 8, StateName = "Japan_State2"},
new State { StateId = 9, StateName = "Japan_State3"}
}
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
}
XALM :
<StackPanel Orientation="Horizontal" >
<DataGrid AutoGenerateColumns="True"
Height="300" Width="300"
HorizontalAlignment="Left" Margin="30"
ItemsSource="{Binding CountriesCollection}"
SelectedItem="{Binding SelectedCountry}">
</DataGrid>
<DataGrid AutoGenerateColumns="True"
Height="300" Width="300"
HorizontalAlignment="Left" Margin="30"
ItemsSource="{Binding SelectedCountry.States}">
</DataGrid>
</StackPanel>
Here I have AutoGenerateColumns property of DataGrid but you have to change it as per your requirement.
I hope this sample code will make things easy to understand for you.
The simplest and cleanest way I found to do this sort of master-details binding on collections is to wrap ObservableCollection in a class, expose its ListCollectionView and bind your ItemsSource to it, like below (it has some extra code that is used to simplify xml serialization):
public class ViewableCollection<T> : ObservableCollection<T>
{
private ListCollectionView _View;
public ViewableCollection(IEnumerable<T> items)
: base(items) { }
public ViewableCollection()
: base() { }
[XmlIgnore]
public ListCollectionView View
{
get
{
if (_View == null)
{
_View = new ListCollectionView(this);
_View.CurrentChanged += new EventHandler(InnerView_CurrentChanged);
}
return _View;
}
}
[XmlIgnore]
public T CurrentItem
{
get
{
return (T)this.View.CurrentItem;
}
set
{
this.View.MoveCurrentTo(value);
}
}
private void InnerView_CurrentChanged(object sender, EventArgs e)
{
this.OnPropertyChanged(new PropertyChangedEventArgs("CurrentItem"));
}
public void AddRange(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
foreach (T item in range)
{
this.Items.Add(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)range.ToList()));
}
public void ReplaceItems(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
this.Items.Clear();
foreach (T item in range)
{
this.Items.Add(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RemoveItems(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
foreach (T item in range)
{
this.Items.Remove(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (IList)range.ToList()));
}
public void ClearAll()
{
IList old = this.Items.ToList();
base.Items.Clear();
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old));
}
public void CallCollectionChaged()
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
// necessary for xml easy serialization using [XmlArray] attribute
public static implicit operator List<T>(ViewableCollection<T> o)
{
return o == null ? default(List<T>) : o.ToList();
}
// necessary for xml easy serialization using [XmlArray] attribute
public static implicit operator ViewableCollection<T>(List<T> o)
{
return o == default(List<T>) || o == null ? new ViewableCollection<T>() : new ViewableCollection<T>(o);
}
}
Then in your ViewModel (Remember to implement INotifyPropertyChanged, I use a nuget package Fody.PropertyChanged to automatically implement it on properties):
[ImplementPropertyChanged]
public class MyViewModel
{
public ViewableCollection<MySecondViewModel> Collection1 { get; set; }
public MyViewModel()
{
this.Collection1 = new ViewableCollection<MySecondViewModel>();
}
}
[ImplementPropertyChanged]
public class MySecondViewModel
{
public string MyColumn1 { get; set; }
public string MyColumn2 { get; set; }
public ViewableCollection<MyThirdViewModel> Collection2 { get; set; }
public MySecondViewModel()
{
this.Collection1 = new ViewableCollection<MyThirdViewModel>();
}
}
[ImplementPropertyChanged]
public class MyThirdViewModel
{
public string MyColumn1 { get; set; }
public string MyColumn2 { get; set; }
}
//...
this.DataContext = new MyViewModel();
Then, keeping your grids synchronized is as simple as this:
<DataGrid ItemsSource="{Binding Collection1.View, Mode=OneWay}"
IsSynchronizedWithCurrentItem="True" />
<DataGrid ItemsSource="{Binding Collection1.CurrentItem.Collection2.View, Mode=OneWay}"
IsSynchronizedWithCurrentItem="True" />
For example binding to a Column1 property in currently selected item in currently selected Collection2 will be:
<TextBlock Text="{Binding Collection1.CurrentItem.Collection2.CurrentItem.Column1}" />
Also, you can manage selection in your code behind, for example:
Collection1.CurrentItem=null;
will clear the selection on the collection.
You can also sort (and filter and group) the ViewableCollection from code behind like this:
Collection1.View.SortDescriptions.Add(new SortDescription("Column1",ListSortDirection.Ascending));
Just remember that you should't replace the whole ViewableCollection after it's been instantiated, just add/remove items from it (for this purpose there is the method AddRange and ReplaceItems for adding/replacing items in bulk without rising CollectionChanged events unnecessarily)

WPF Multiple ViewModels and Multiple UserCotnrols

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.

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);

Simple WPF data binding question with icons

I have a few types that make up a hierarchy like this:
+ Image0.Name
Effect0.Name
Effect1.Name
Effect2.Name
Layer0.Name
Layer1.Name
Layer2.Name
...
+ Image1.Name
Effect0.Name
Effect1.Name
Effect2.Name
Layer0.Name
Layer1.Name
Layer2.Name
...
+ Image2.Name
Effect0.Name
Effect1.Name
Effect2.Name
Layer0.Name
Layer1.Name
Layer2.Name
...
But I can't get my head around the data binding. Here is the code for the types:
public class Image
{
public string Name { get; set; }
public IEnumerable<Effect> Effects { get; set; }
public IEnumerable<Layer> Layers { get; set; }
}
public class Effect
{
public string Name { get; set; }
public Effect ( string name )
{
this.Name = name;
}
}
public class Layer
{
public string Name { get; set; }
public Layer ( string name )
{
this.Name = name;
}
}
public class EditorView : INotifyPropertyChanged
{
IEnumerable<Node> images;
public IEnumerable<Node> Images
{
get { return images; }
set
{
this.images = value;
this.RaisePropertyChanged ( "Images" );
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged ( string propertyName )
{
var handler = this.PropertyChanged;
if ( handler != null )
handler ( this, new PropertyChangedEventArgs ( propertyName ) );
}
}
Additionally these types (Effect, Layer) has a unique icon per type, if you can also show how to bind this, that would help me a lot in understanding it all.
This is how I normally do it, create a base class for the child items and then create I property that returns all the child Items
public class Image
{
public string Name { get; set;}
public IEnumerable<Effect> Effects { get; set; }
public IEnumerable<Layer> Layers { get; set; }
public IEnumerable<Node> Nodes { get { return ((IEnumerable<Node>)Layers).Union((IEnumerable<Node>)Effects); } }
}
public class Effect : Node
{
public Effect(string name)
{
this.Name = name;
}
}
public class Layer : Node
{
public Layer(string name) { this.Name = name; }
}
public class Node
{
public string Name { get; set; }
public Image Icon { get; set; }
}
You should be able to set the Image Property (url of image but you can change the property type) of the respective Effects and Layers and then I've already wired it up, this should work
<TreeView Height="221" HorizontalAlignment="Left" Margin="12,12,0,0" Name="treeView1"
VerticalAlignment="Top" Width="479" ItemsSource="{Binding Images}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
<Image Source="{Binding Icon}"></Image>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
EDIT
And then set the TreeView Item's DataContext to your ViewModel, just as an example I did this in the code behind:
Image img = new Image();
Effect effect = new Effect("Effect1");
Layer layer = new Layer("Layer1");
img.Name = "Image1";
List<Effect> effects = new List<Effect>();
effects.Add(effect);
img.Effects = effects;
List<Layer> layers = new List<Layer>();
layers.Add(layer);
img.Layers = layers;
List<WpfApplication1.Image> Images = new List<Image>();
Images.Add(img);
EditorView ev = new EditorView();
ev.Images = Images;
treeView1.DataContext = ev;
EDIT2: Pasted complete code (without using statements):
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new EditorView();
}
}
public class Image
{
public string Name { get; set;}
public IEnumerable<Effect> Effects { get; set; }
public IEnumerable<Layer> Layers { get; set; }
public IEnumerable<Node> Nodes { get { return ((IEnumerable<Node>)Layers).Union((IEnumerable<Node>)Effects); } }
}
public class Effect : Node
{
public Effect(string name)
{
this.Name = name;
}
}
public class Layer : Node
{
public Layer(string name) { this.Name = name; }
}
public class Node
{
public string Name { get; set; }
public string Icon { get; set; }
}
public class EditorView : INotifyPropertyChanged
{
public EditorView()
{
Image img = new Image();
WpfApplication1.Effect effect = new WpfApplication1.Effect("Effect1");
WpfApplication1.Layer layer = new Layer("Layer1");
img.Name = "Image1";
List<Effect> effects = new List<WpfApplication1.Effect>();
effects.Add(effect);
img.Effects = effects;
List<Layer> layers = new List<Layer>();
layers.Add(layer);
img.Layers = layers;
List<WpfApplication1.Image> Images = new List<Image>();
Images.Add(img);
this.Images = Images;
}
IEnumerable<Image> images;
public IEnumerable<Image> Images
{
get
{
return images;
}
set { this.images = value; this.RaisePropertyChanged("Images");
}
} public event
PropertyChangedEventHandler
PropertyChanged;
void RaisePropertyChanged(string propertyName)
{ var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You need to implement the HierarchicalDataTemplate. For sample of it look at the last of this article - http://msdn.microsoft.com/en-us/library/ms742521.aspx and this one - http://blogs.msdn.com/b/chkoenig/archive/2008/05/24/hierarchical-databinding-in-wpf.aspx

Categories

Resources