I have a ListView and a button AddNewItem and two forms both wrapped in two stack panels: spanel1 and spanel2
when I select an item from the list, spanel1 shows data of the selected item
when I click on the button the form of spanel2 is empty
in the view model I have two properties: selectedItem and newItem
Problem:
if I fill data of the new item in the form of spanel2 and click Save (see Save command code) the NewItem property will not be changed
private void SaveItem(object obj)
{
if (this.selectedItem != null)
{
this.item.EditAsync(this.selectedItem );
}
else
{
this.ietm.InsertAsync(this.NewItem);
}
this.LoadData();
}
private Item newItem;
public Item NewItem
{
get
{
return this.newItem ?? new Item();
}
set
{
this.newItem = value;
this.RaisePropertyChanged("NewItem");
}
}
private Item selectedItem;
public Item SelectedItem
{
get
{
return this.selectedItem;
}
set
{
this.selectedItem= value;
this.RaisePropertyChanged("SelectedItem");
}
}
what am I doing wrong?
Thanks.
1) create a new button in XAML
2) create a new command in UserVM class(call it UpdateOrAddNew command) and associate it with the button using "Command" property.
See this for more information: How to bind WPF button to a command in ViewModelBase?
3) In your UpdateOrAddNew command handler, check if SelectedUser is null(then create new user) or if it's not null, update existing one.
To take car of the TextBox issue, you need to create a wrapper that holds always information.
Add this to your UserVM:
public User UserRecord
{
get
{
if(userRecord == null)
return userRecord = new User();
}
set
{
userRecord = value;
onPropertyChanged("UserRecord");
}
}
now you need to modify your TextBoxes to bind against UserRecord, instead of SelectedUser.
and modify your SelectedUser:
public User SelectedUser
{
get { return selecteduser; }
set
{
selecteduser = value;
onPropertyChanged("SelectedUser");
UserRecord = selecteduser;
}
}
Related
I'm getting stuck into MVVM in WPF and I have setup an ObservableCollection and an ICollectionView. The ICollectionView is set as the ItemsSource of a DataGrid, and the model is a type of Job.
I've setup getters and setter for both of the collections however when I am setting a Filter on the ICollectionView instead of the Job being filtered by the SearchString they're just replicated over and over again, leading me to believe that they way I have the collections setup is totally wrong.
Here is how the two collections are get/set:
public ObservableCollection<Job> AllJobs
{
get
{
foreach (var job in _allJobsList)
_allJobs.Add(job);
return _allJobs;
}
set
{
if (_allJobs == value) return;
OnPropertyChanged("AllJobs");
}
}
public ICollectionView AllJobsView
{
get
{
_allJobsView = CollectionViewSource.GetDefaultView(AllJobs);
return _allJobsView;
}
set
{
if (_allJobsView == value)
{
return;
}
_allJobsView = value;
OnPropertyChanged("AllJobsView");
}
}
Now I have a stringcalled SearchString that is bound to a TextBox.Text. When the text changes I do the following:
public string SearchString
{
get => _searchString;
set
{
if (_searchString == value) return;
_searchString = value;
FilterJobs();
OnPropertyChanged("SearchString");
}
}
private void FilterJobs()
{
AllJobsView.Filter = x =>
{
var viewJob = x as Job;
return viewJob != null && viewJob.Number.Contains(_searchString);
};
}
Now when the page first loads, there are the correct Jobs loaded into the DataGrid. However, as soon as the user types the Jobs are duplicated if the Job.Number does contain the SearchString. How am I able to setup the collections so that I can appropriately set a filter?
The problem is in the getter of your ObservableCollection. Every time you "get" the collection, you are adding every item to the collection all over again.
Your code:
get
{
foreach (var job in _allJobsList)
_allJobs.Add(job);
return _allJobs;
}
Instead, it should be:
get
{
return _allJobs;
}
The setter of your ObservableCollection is also missing the "setter" (private field = value) code:
set
{
if (value != _allJobs)
{
_allJobs = value;
OnPropertyChanged("AllJobs");
}
}
Your Property AllJobs would then be:
private ObservableCollection<Job> _allJobs;
public ObservableCollection<Job> AllJobs
{
get
{
return _allJobs;
}
set
{
if (value != _allJobs)
{
_allJobs = value;
OnPropertyChanged("AllJobs");
}
}
}
The initialization of your collection should be someplace else (and not in the getter of your property), like in the constructor of the ViewModel or/and in a method that a command calls after the user asks for a refresh of the collection.
For example, if your VieModel is called MyViewModel and your List<Job> is called _allJobsList, you can initialize your collection like so:
public MyViewModel()
{
//fill the _allJobsList first, getting from a database for example: _allJobsList = GetJobs();
//and then create an observable collection from that list
AllJobs = new ObservableCollection<Job>(_allJobsList);
}
I want to Update ListCollectionView in a listbox each time the Item of another ListCollection gets selected.
I have 2 ListViewCollection, SceneCollectionView and ShotCollectionView. I want to have the SceneCollection filtered based on a property SceneNumber in ShotCollectionView, but I can get the ShotCollectionView to update when I go from one item to the other in SceneCollectionView.
This is my ViewModel
public class ShotListViewModel : NotifyUIBase
{
public ListCollectionView SceneCollectionView { get; set; }
private Scenes CurrentScene
{
get { return SceneCollectionView.CurrentItem as Scenes; }
set { SceneCollectionView.MoveCurrentTo(value); RaisePropertyChanged(); }
}
private ObservableCollection<Shot> _allShots = new ObservableCollection<Shot>();
public ObservableCollection<Shot> AllShots
{
get { return _allShots; }
set { _allShots = value; RaisePropertyChanged();}
}
private ListCollectionView _allShotsCollection;
public ListCollectionView AllShotsCollection
{
get
{
if (_allShotsCollection == null)
{
_allShotsCollection = new ListCollectionView(this.AllShots);
_allShotsCollection.Filter = IsSceneNumber;
}
return _allShotsCollection;
}
}
private bool IsSceneNumber(object obj)
{
if (obj as Shot != null
&& (obj as Shot).SceneNumber == (SceneCollectionView.CurrentItem as Scene).SceneNumber)
{
return true;
}
return false;
}
public ShotListViewModel()
{
SceneCollectionView = Application.Current.Resources["SceneCollectionView"] as ListCollectionView;
GetShotList(); //Populates the AllShots Observable collection.
AddShotCommand = new RelayCommand(AddShot);
FilterShotsCommand = new RelayCommand(AddShot);
}
What am I missing here to make it work or is it better to use ICollectionViewLiveShaping. but I have no idea how to implement that
I don`t understand what you tried to do, but lets talk on an example :
Lets say we have
ListBox1 binded to ListBox1Items and
ListBox2 binded to ListBox2Items.
if you want to filter the data from ListBox2 you have to filter ListBox2Items. How to do that ? Is simple : ListBox1 has a property SelectedItem which you can bind to --- lets say --- ListBox1SelectedItem. Every time when selection change, in the setter of the ListBox1SelectedItem you can trigger a filter on ListBox2Items.
Hope you understand what I`ve explained.
I have a Window with a TabControl in it. The TabControl contains 5 different TabItems. Each TabItem has its own ViewModel associated as its DataContext, while the Window has a DataContext that has all 5 TabItem's view models as properties. The problem I am having is setup. There is a noticeable lag when I launch the Window (from my MainWindow) and I have spent a good chunk of time refactoring my code and making it faster by running things in parallel, making fewer calls to the database and running Tasks on semi-expensive operations. Everything works great, except for one TabItem and its view model. For some reason, the view does not refresh itself properly.
For instance, I have a view model called DiaryDescriptionViewModel that takes in List<SectionViewModel> and does stuff with it, with the view bound to a result collection. It works fine. My troublesome view model is called DiaryPayItemEditorViewModel and it too takes in a List<SectionViewModel> and does stuff with it, with the view bound to a result collection. Neither view models perform the work on List<SectionViewModel> on worker threads or anything. However both view models are instanced and set up in parallel, which I wouldn't think be the root of the problem.
In my DiaryPayItemEditorViewModel, I have a ObservableCollection<DiaryPayItemDetailViewModel> that a ListView is data bound to. The ListView never displays the data, even though it exists. If I take all of my view model initialization code out of a Parallel.Invoke call, then it binds and displays the data.
My assumption here is that the view is initialized (this.InitializeComponents) before the DiaryPayItemEditorViewModel is fully set up, which should be fine. Since my view models all implement INotifyPropertyChanged, the view should be notified that changes have taken place. For the life of me, I can't figure this out.
The following is the applicable source for the view Window view model (DiaryEditorViewModel), the view model who uses the same collection and works with binding (DiaryDescriptionViewModel and its child DiaryDescriptionDetailsViewModel) and then my troublesome view model (DiaryPayItemEditorViewModel and its child DiaryPayItemDetailViewModel).
DiaryEditorViewModel.cs
public class DiaryEditorViewModel : BaseChangeNotify
{
private DiaryViewModel diary;
private Project project;
private DiaryDetailsViewModel diaryDetailsViewModel;
private DiaryDescriptionViewModel diaryDescriptionViewModel;
private DiaryPayItemEditorViewModel diaryPayItemsViewModel;
private DiaryEquipmentEditorViewModel diaryEquipmentEditorViewModel;
private DiaryLaborViewModel diaryLaborViewModel;
// This is the designated constructor used by the app.
public DiaryEditorViewModel(Project project, Diary diary, UserViewModel user)
: base(user)
{
// Instance a new diary view model using the provided diary.
this.diary = new DiaryViewModel(diary, user);
this.project = project;
// Setup the repositories we will use.
var repository = new ProjectRepository();
var contractorRepository = new ContractorRepository();
// Setup the temporary collections used by the repositories.
var contractors = new List<Contractor>();
var contractorViewModels = new List<ContractorViewModel>();
var projectSections = new List<Section>();
var bidItemCollection = new List<BidItem>();
var subItemCollection = new List<SubItem>();
var sectionViewModels = new List<SectionViewModel>();
var equipmentCategories = new List<EquipmentCategory>();
var equipmentFuelTypes = new List<EquipmentFuelType>();
var equipmentList = new List<Equipment>();
var equipmentViewModels = new List<EquipmentViewModel>();
Task.Run(() =>
{
Parallel.Invoke(
// Fetch contractors for selected project.
() =>
{
contractors.AddRange(contractorRepository.GetContractorsByProjectId(diary.ProjectId));
equipmentCategories.AddRange(contractorRepository.GetEquipmentCategories());
equipmentFuelTypes.AddRange(contractorRepository.GetEquipmentFuelTypes());
equipmentList.AddRange(contractorRepository.GetEquipmentByProjectId(this.Project.ProjectId));
// Reconstruct the contractor->Equipment->FuelType & Category relationship.
contractorViewModels.AddRange(
contractors.Select(contractor =>
new ContractorViewModel(
contractor,
equipmentList.Where(equipment =>
equipment.ContractorId == contractor.ContractorId).Select(e =>
new EquipmentViewModel(
e,
contractor,
equipmentCategories.FirstOrDefault(cat =>
cat.EquipmentCategoryId == e.EquipmentCategoryId),
equipmentFuelTypes.FirstOrDefault(f =>
f.EquipmentFuelTypeId == e.EquipmentFuelTypeId))))));
},
() =>
{
// Fetch all of the Sections, Bid-Items and Sub-items for the project
projectSections.AddRange(repository.GetSectionsByProjectId(project.ProjectId));
bidItemCollection.AddRange(repository.GetBidItemsByProjectId(project.ProjectId));
subItemCollection.AddRange(repository.GetSubItemsByProjectId(project.ProjectId));
// Reconstruct the Section->BidItem->SubItem hierarchy.
sectionViewModels.AddRange(
projectSections.Select(s =>
new SectionViewModel(project, s,
bidItemCollection.Where(b => b.SectionId == s.SectionId).Select(b =>
new BidItemViewModel(project, b,
subItemCollection.Where(si => si.BidItemId == b.BidItemId))))));
}
);
// Once the parallel invocations are completed, instance all of the children view models
// using the view model collections we just set up.
Parallel.Invoke(
// Fetch contractors for selected project.
() =>
this.DiaryDetailsViewModel = new DiaryDetailsViewModel(
project,
diary,
user),
() => // This view model works just fine, with same constructor signature.
this.DiaryDescriptionViewModel = new DiaryDescriptionViewModel(
project,
diary,
user,
sectionViewModels),
() =>
this.DiaryPayItemEditorViewModel = new DiaryPayItemEditorViewModel(
project,
diary,
user,
sectionViewModels),
() => // This view model does not notify the UI of changes to its collection.
this.DiaryEquipmentEditorViewModel = new DiaryEquipmentEditorViewModel(
project,
diary,
user,
contractorViewModels),
() =>
// For the Labor view, we just pass the Contractor model collection rather than the view model collection
// since the Labor view does not need any of the additional equipment information.
this.DiaryLaborViewModel = new DiaryLaborViewModel(
project,
diary,
user,
contractors));
});
}
public Project Project
{
get
{
return this.project;
}
set
{
this.project = value;
this.OnPropertyChanged();
}
}
public DiaryViewModel Diary
{
get
{
return this.diary;
}
set
{
this.diary = value;
this.OnPropertyChanged();
}
}
public DiaryDetailsViewModel DiaryDetailsViewModel
{
get
{
return this.diaryDetailsViewModel;
}
set
{
this.diaryDetailsViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryDescriptionViewModel DiaryDescriptionViewModel
{
get
{
return this.diaryDescriptionViewModel;
}
set
{
this.diaryDescriptionViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryPayItemEditorViewModel DiaryPayItemEditorViewModel
{
get
{
return this.diaryPayItemsViewModel;
}
set
{
this.diaryPayItemsViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryLaborViewModel DiaryLaborViewModel
{
get
{
return this.diaryLaborViewModel;
}
set
{
this.diaryLaborViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryEquipmentEditorViewModel DiaryEquipmentEditorViewModel
{
get
{
return this.diaryEquipmentEditorViewModel;
}
set
{
this.diaryEquipmentEditorViewModel = value;
this.OnPropertyChanged();
}
}
}
DiaryDescriptionViewModel
This view model works just fine, with its this.DiaryDescriptions collection being bound to properly and displayed in the ListView
public class DiaryDescriptionViewModel : BaseDiaryViewModel, IDataErrorInfo
{
private ObservableCollection<DiaryDescriptionDetailsViewModel> diaryDescriptions;
private DiaryDescriptionDetailsViewModel selectedDiaryDescription;
public DiaryDescriptionViewModel()
{
}
public DiaryDescriptionViewModel(Project project, Diary diary, UserViewModel user, List<SectionViewModel> sections)
: base(project, diary, user)
{
// Restore any previously saved descriptions.
var diaryRepository = new DiaryRepository();
List<DiaryDescription> descriptions = diaryRepository.GetDiaryDescriptionsByDiaryId(diary.DiaryId);
this.ProjectSections = sections;
// Reconstruct our descriptions
this.diaryDescriptions = new ObservableCollection<DiaryDescriptionDetailsViewModel>();
foreach (DiaryDescription description in descriptions)
{
SectionViewModel section = this.GetSectionContainingBidItemId(description.BidItemId);
BidItemViewModel bidItem = section.GetBidItem(description.BidItemId);
var details = new DiaryDescriptionDetailsViewModel(description, section, bidItem);
details.PropertyChanged += ChildViewModelPropertyChanged;
this.diaryDescriptions.Add(details);
}
this.diaryDescriptions.CollectionChanged += this.DiaryDescriptionsOnCollectionChanged;
this.IsDirty = false;
}
public ObservableCollection<DiaryDescriptionDetailsViewModel> DiaryDescriptions
{
get
{
return this.diaryDescriptions;
}
set
{
if (value != null)
{
this.diaryDescriptions.CollectionChanged -= this.DiaryDescriptionsOnCollectionChanged;
this.diaryDescriptions =
new ObservableCollection<DiaryDescriptionDetailsViewModel>(
value
.OrderBy(s => s.Section.Section)
.ThenBy(i => i.BidItem.BidItem.Number));
this.diaryDescriptions.CollectionChanged += this.DiaryDescriptionsOnCollectionChanged;
}
else
{
this.diaryDescriptions = new ObservableCollection<DiaryDescriptionDetailsViewModel>();
}
this.OnPropertyChanged();
}
}
public DiaryDescriptionDetailsViewModel SelectedDiaryDescription
{
get
{
return this.selectedDiaryDescription;
}
set
{
// Always unsubscribe from events before replacing the object. Otherwise we end up with a memory leak.
if (this.selectedDiaryDescription != null)
{
this.selectedDiaryDescription.PropertyChanged -= this.ChildViewModelPropertyChanged;
}
this.selectedDiaryDescription = value;
if (value != null)
{
// If the description contains a biditem DiaryId, then we go fetch the section and biditem
// associated with the diary description.
if (value.BidItemId > 0)
{
this.selectedDiaryDescription.Section = this.GetSectionContainingBidItemId(value.BidItemId);
this.selectedDiaryDescription.BidItem = this.selectedDiaryDescription.Section.GetBidItem(value.BidItemId);
}
// Subscribe to property changed events so we can set ourself to dirty.
this.selectedDiaryDescription.PropertyChanged += this.ChildViewModelPropertyChanged;
this.selectedDiaryDescription.IsDirty = false;
}
this.OnPropertyChanged();
this.IsDirty = false;
}
}
DiaryDescriptionDetailViewModel
Working child view model.
public class DiaryDescriptionDetailsViewModel : BaseChangeNotify
{
private readonly DiaryDescription diaryDescription;
private SectionViewModel section;
private BidItemViewModel bidItem;
public DiaryDescriptionDetailsViewModel(DiaryDescription description, SectionViewModel section = null, BidItemViewModel bidItem = null)
{
this.diaryDescription = description;
if (description.BidItemId > 0)
{
this.section = section;
this.bidItem = bidItem;
}
this.IsDirty = false;
}
public DiaryDescription Description
{
get
{
return this.diaryDescription;
}
}
public int BidItemId
{
get
{
return this.diaryDescription.BidItemId;
}
}
public BidItemViewModel BidItem
{
get
{
return this.bidItem;
}
set
{
this.bidItem = value;
this.diaryDescription.BidItemId = value.BidItem.BidItemId;
this.OnPropertyChanged();
}
}
public SectionViewModel Section
{
get
{
return this.section;
}
set
{
this.section = value;
this.OnPropertyChanged();
}
}
}
DiaryPayItemEditorViewModel
And finally, the view model who is not having its collection rendered to the view.
public class DiaryPayItemEditorViewModel : BaseDiaryViewModel, IDataErrorInfo
{
private ObservableCollection<DiaryPayItemDetailViewModel> diaryPayItemDetails;
private DiaryPayItemDetailViewModel selectedDiaryPayItemDetail;
private List<DiaryPayItem> allPayItemsForSelectedBidItem;
private decimal sumOfAllPayItemsForBidItem;
public DiaryPayItemEditorViewModel()
{
}
public DiaryPayItemEditorViewModel(Project project, Diary diary, UserViewModel user, List<SectionViewModel> sections)
: base(project, diary, user)
{
this.Initialize(project, sections);
this.IsDirty = false;
}
public ObservableCollection<DiaryPayItemDetailViewModel> DiaryPayItemDetails
{
get
{
return this.diaryPayItemDetails;
}
set
{
this.diaryPayItemDetails = value;
this.OnPropertyChanged();
}
}
public DiaryPayItemDetailViewModel SelectedDiaryPayItemDetail
{
get
{
return this.selectedDiaryPayItemDetail;
}
set
{
if (this.selectedDiaryPayItemDetail != null)
{
this.selectedDiaryPayItemDetail.PropertyChanged -= this.ChildViewModelPropertyChanged;
}
if (value != null)
{
value.PropertyChanged += this.ChildViewModelPropertyChanged;
}
this.selectedDiaryPayItemDetail = value;
this.OnPropertyChanged();
}
}
private void Initialize(Project project, List<SectionViewModel> sections)
{
var repository = new DiaryRepository();
var projectRepository = new ProjectRepository();
this.DiaryPayItemDetails = new ObservableCollection<DiaryPayItemDetailViewModel>();
this.ProjectSections = sections;
// Repository calls to the database.
List<DiaryPayItem> payItems = repository.GetDiaryPayItemsByDiaryId(this.Diary.DiaryId);
var sectionItems = projectRepository.GetSectionHierarchy(project.ProjectId);
// Temporary, needs to be refined.
foreach (var diaryPayItem in payItems)
{
var subItem = sectionItems.SubItems.FirstOrDefault(sub => sub.SubItemId == diaryPayItem.SubItemId);
var bidItems =
sectionItems.BidItems.Where(bid => bid.BidItemId == subItem.BidItemId)
.Select(
bid =>
new BidItemViewModel(project, bid,
sectionItems.SubItems.Where(sub => sub.BidItemId == bid.BidItemId)));
var section = new SectionViewModel(
project,
sectionItems.Sections.FirstOrDefault(s => bidItems.Any(bid => bid.BidItem.SectionId == s.SectionId)),
bidItems);
this.DiaryPayItemDetails.Add(
new DiaryPayItemDetailViewModel(
diaryPayItem,
section,
bidItems.FirstOrDefault(bid => bid.BidItem.BidItemId == subItem.BidItemId),
subItem));
}
}
DiaryPayItemDetailViewModel - Child view model to the troublesome view model
public class DiaryPayItemDetailViewModel : BaseChangeNotify
{
private DiaryPayItem diaryPayItem;
private SectionViewModel selectedSection;
private BidItemViewModel selectedBidItem;
private SubItem selectedSubItem;
public DiaryPayItemDetailViewModel(
DiaryPayItem diaryPayItem,
SectionViewModel section,
BidItemViewModel bidItem,
SubItem subItem)
{
this.DiaryPayItem = diaryPayItem;
this.SelectedSection = section;
this.SelectedBidItem = bidItem;
this.SelectedSubItem = subItem;
}
public DiaryPayItem DiaryPayItem
{
get
{
return this.diaryPayItem;
}
set
{
this.diaryPayItem = value;
this.OnPropertyChanged();
}
}
public SectionViewModel SelectedSection
{
get
{
return this.selectedSection;
}
set
{
this.selectedSection = value;
this.OnPropertyChanged();
}
}
public BidItemViewModel SelectedBidItem
{
get
{
return this.selectedBidItem;
}
set
{
this.selectedBidItem = value;
this.OnPropertyChanged();
}
}
public SubItem SelectedSubItem
{
get
{
return this.selectedSubItem;
}
set
{
this.selectedSubItem = value;
this.DiaryPayItem.SubItemId = value.SubItemId;
this.OnPropertyChanged();
}
}
XAML for the DiaryDescription Tab Item.
<ListView ItemsSource="{Binding Path=DiaryDescriptions}"
SelectedItem="{Binding Path=SelectedDiaryDescription}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Section.SectionName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
XAML for Diary Pay Items tab item.
<ListView Name="PayItemListView"
ItemsSource="{Binding Path=DiaryPayItemDetails}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=SelectedBidItem.BidItem.Description}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
BaseChangeNotify
Lastly, to show my INotifyPropertyChanged implementation, I present my base class. It wraps all calls to the event handlers in an Application.Current.Dispatcher.Invoke() action. This forces all event handler calls to be ran on the main thread so I don't have to worry about cross-thread issues in my inherited objects.
public class BaseChangeNotify : INotifyPropertyChanged
{
private bool isDirty;
private UserViewModel user;
public BaseChangeNotify()
{
}
public BaseChangeNotify(UserViewModel user)
{
this.user = user;
}
public event PropertyChangedEventHandler PropertyChanged;
public bool IsDirty
{
get
{
return this.isDirty;
}
set
{
this.isDirty = value;
this.OnPropertyChanged();
}
}
public UserViewModel User
{
get
{
return this.user;
}
}
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
// Perform the IsDirty check so we don't get stuck in a infinite loop.
if (propertyName != "IsDirty")
{
this.IsDirty = true; // Each time a property value is changed, we set the dirty bool.
}
if (this.PropertyChanged != null)
{
// Invoke the event handlers attached by other objects.
try
{
// When unit testing, this will always be null.
if (Application.Current != null)
{
Application.Current.Dispatcher.Invoke(() =>
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
else
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
catch (Exception)
{
throw;
}
}
}
If anyone could help me figure this out I would greatly appreciate it. I have been trying various things for the last two days and can't get it figured out. It's weird how one view model works fine, essentially performing the same kind of operation, and the other does not.
Thanks in advance.
The DiaryEditorViewModel is the view model to a DiaryEditorWindow. The DiaryPayItemEditorViewModel belongs to a user control that resides within the Window. Setting the data context in the XAML at the Window level, for the TabItem, resolved this issue. Setting the DataContext at the UserControl level caused view model to not bind properly.
I also tried setting the datacontext in the constructor, but that had the same issue. It would never bind. By setting the datacontext in the XAML of the TabItem associated with the troublesome view model, the problem was resolved. I don't understand why this is an issue. Since the view model fully implements the property changed event, I should be able to set the data context at any point, and adjust the values without a problem.
Eitherway, I have been able to solve this issue.
I have create two Combobox column in gridview. Now i want to fill second combobox depending on value of first combobox (On first combobox selectedValueChanged event). Please reply.
General approach may look like this:
private MyType1 _selectedItem1;
public MyType1 SelectedItem1
{
get { return _selectedItem1; }
set
{
if (_selectedItem1 == value) return;
_selectedItem1 = value;
//replace with string implementation, if needed
OnPropertyChanged(() => SelectedItem1);
if (_selectedItem1 == ...)
{
ItemsSource2 = new List<MyClass2> { ... };
}
else if (_selectedItem1 == ...)
{
...
}
}
}
private IList<MyType2> _itemsSource2;
public IList<MyType2> ItemsSource2
{
get { return _itemsSource2; }
set
{
if (_itemsSource2 == value) return;
_itemsSource2 = value;
OnPropertyChanged(() => ItemsSource2);
}
}
I'm not quite sure how to deal with this problem. I'm using a bunch of comboboxes with dropdown lists of values we allow the user to set a property too. (i.e. Currencies = "USD, CAD, EUR").
Every now and then, when we load data, we'll find the currency is something not in our list, like "AUD". In this case, we still want the combobox to display the loaded value, and the current selected Currency should remain "AUD" unless the user chooses to change it, in which case their only options will still be "USD, CAD, EUR".
My problem is that as soon as the control becomes visible, the ComboBox is calling the setter on my SelectedCurrency property and setting it to null, presumably because the current value "AUD" isn't in it's list. How can I disable this behaviour without making it possible for the user to type whatever they want into the Currency field?
Set IsEditable="True", IsReadOnly="True", and your SelectedItem equal to whatever object you want to hold the selected item
<ComboBox ItemsSource="{Binding SomeCollection}"
Text="{Binding CurrentValue}"
SelectedItem="{Binding SelectedItem}"
IsEditable="True"
IsReadOnly="True">
IsEditable allows the Text property to show a value not in the list
IsReadOnly makes it so the Text property is not editable
And SelectedItem stores the selected item. It will be null until the user selects an item in the list, so in your SaveCommand, if SelectedItem == null then use CurrentValue instead of SelectedItem when saving to the database
This seems to be a reasonably common problem. Imagine you have a lookup list in the database, maybe a list of employees. The employee table has a 'works here' flag. Another table references the employee lookup list. When a person leaves the company, you want your views to show the name of the old employee, but not allow the old employee to be assigned in future.
Here's my solution to the similar currency problem:
Xaml
<Page.DataContext>
<Samples:ComboBoxWithObsoleteItemsViewModel/>
</Page.DataContext>
<Grid>
<ComboBox Height="23" ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"/>
</Grid>
C#
// ViewModelBase and Set() are from MVVM Light Toolkit
public class ComboBoxWithObsoleteItemsViewModel : ViewModelBase
{
private readonly string _originalCurrency;
private ObservableCollection<string> _items;
private readonly bool _removeOriginalWhenNotSelected;
private string _selectedItem;
public ComboBoxWithObsoleteItemsViewModel()
{
// This value might be passed in to the VM as a parameter
// or obtained from a data service
_originalCurrency = "AUD";
// This list is hard-coded or obtained from your data service
var collection = new ObservableCollection<string> {"USD", "CAD", "EUR"};
// If the value to display isn't in the list, then add it
if (!collection.Contains(_originalCurrency))
{
// Record the fact that we may need to remove this
// value from the list later.
_removeOriginalWhenNotSelected = true;
collection.Add(_originalCurrency);
}
Items = collection;
SelectedItem = _originalCurrency;
}
public string SelectedItem
{
get { return _selectedItem; }
set
{
// Remove the original value from the list if necessary
if(_removeOriginalWhenNotSelected && value != _originalCurrency)
{
Items.Remove(_originalCurrency);
}
Set(()=>SelectedItem, ref _selectedItem, value);
}
}
public ObservableCollection<string> Items
{
get { return _items; }
private set { Set(()=>Items, ref _items, value); }
}
}
You should set IsEditable of the ComboBox to true and bind the Text property instead of the SelectedValue property.
If IsEditable = false then ComboBox does not support a value not in the list.
If you want user action to add a value but not edit that value or any existing values then one approach might be to put the new value in TextBlock (not editable) and a Button to let them add that value. If they select any value from the combobox then hide the TextBlock and Button.
Another approach would be to add the value to the list with more complex logic behind that if any another value is selected then that tentative value is removed. And the tentative value does not get persisted until it is selected.
He doesn't want to be users to be able to type, so IsEditable seems to be off the table.
What I would do is just add the new value AUD to the Item list as
ComboBoxItem Content="AUD" Visibility="Collapsed"
Then Text="AUD" will work in code but not from the drop down.
To be fancy, one could make a converter for the ItemsSource that binds to the TEXT box and adds it collapsed automatically
Here was my solution to this problem:
XAML looks like this:
<DataTemplate>
<local:CCYDictionary Key="{TemplateBinding Content}">
<local:CCYDictionary.ContentTemplate>
<DataTemplate>
<ComboBox Style="{StaticResource ComboBoxCellStyle}"
SelectedValuePath="CCYName"
DisplayMemberPath="CCYName"
TextSearch.TextPath="CCYName"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:CCYDictionary}}, Path=ListItems}"
SelectedValue="{Binding}" />
</DataTemplate>
</local:CCYDictionary.ContentTemplate>
</local:CCYDictionary>
</DataTemplate>
<!-- For Completion's sake, here's the style and the datacolumn using it -->
<Style x:Key="ComboBoxCellStyle" TargetType="ComboBox">
<Setter Property="IsEditable" Value="False"/>
<Setter Property="IsTextSearchEnabled" Value="True"/>
<!-- ...other unrelated stuff (this combobox was was a cell template for a datagrid) -->
</Style>
<Column FieldName="CCYcode" Title="Currency" DataTemplate="{StaticResource CCYEditor}" />
There's probably a nicer way for the dictionary to expose the ItemsSource so that the Bindings aren't so ugly, but once I got it to work I was too tired of the problem to refine it further.
Individual dictionaries declared like so:
public class CCYDictionary : DataTableDictionary<CCYDictionary>
{
protected override DataTable table { get { return ((App)App.Current).ApplicationData.CCY; } }
protected override string indexKeyField { get { return "CCY"; } }
public CCYDictionary() { }
}
public class BCPerilDictionary : DataTableDictionary<BCPerilDictionary>
{
protected override DataTable table { get { return ((App)App.Current).ApplicationData.PerilCrossReference; } }
protected override string indexKeyField { get { return "BCEventGroupID"; } }
public BCPerilDictionary() { }
}
//etc...
Base class looks like:
public abstract class DataTableDictionary<T> : ContentPresenter where T : DataTableDictionary<T>
{
#region Dependency Properties
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register("Key", typeof(object), typeof(DataTableDictionary<T>), new PropertyMetadata(null, new PropertyChangedCallback(OnKeyChanged)));
public static readonly DependencyProperty RowProperty = DependencyProperty.Register("Row", typeof(DataRowView), typeof(DataTableDictionary<T>), new PropertyMetadata(null, new PropertyChangedCallback(OnRowChanged)));
public static readonly DependencyProperty ListItemsProperty = DependencyProperty.Register("ListItems", typeof(DataView), typeof(DataTableDictionary<T>), new PropertyMetadata(null));
public static readonly DependencyProperty IndexedViewProperty = DependencyProperty.Register("IndexedView", typeof(DataView), typeof(DataTableDictionary<T>), new PropertyMetadata(null));
#endregion Dependency Properties
#region Private Members
private static DataTable _SourceList = null;
private static DataView _ListItems = null;
private static DataView _IndexedView = null;
private static readonly Binding BindingToRow;
private static bool cachedViews = false;
private bool m_isBeingChanged;
#endregion Private Members
#region Virtual Properties
protected abstract DataTable table { get; }
protected abstract string indexKeyField { get; }
#endregion Virtual Properties
#region Public Properties
public DataView ListItems
{
get { return this.GetValue(ListItemsProperty) as DataView; }
set { this.SetValue(ListItemsProperty, value); }
}
public DataView IndexedView
{
get { return this.GetValue(IndexedViewProperty) as DataView; }
set { this.SetValue(IndexedViewProperty, value); }
}
public DataRowView Row
{
get { return this.GetValue(RowProperty) as DataRowView; }
set { this.SetValue(RowProperty, value); }
}
public object Key
{
get { return this.GetValue(KeyProperty); }
set { this.SetValue(KeyProperty, value); }
}
#endregion Public Properties
#region Constructors
static DataTableDictionary()
{
DataTableDictionary<T>.BindingToRow = new Binding();
DataTableDictionary<T>.BindingToRow.Mode = BindingMode.OneWay;
DataTableDictionary<T>.BindingToRow.Path = new PropertyPath(DataTableDictionary<T>.RowProperty);
DataTableDictionary<T>.BindingToRow.RelativeSource = new RelativeSource(RelativeSourceMode.Self);
}
public DataTableDictionary()
{
ConstructDictionary();
this.SetBinding(DataTableDictionary<T>.ContentProperty, DataTableDictionary<T>.BindingToRow);
}
#endregion Constructors
#region Private Methods
private bool ConstructDictionary()
{
if( cachedViews == false )
{
_SourceList = table;
if( _SourceList == null )
{ //The application isn't loaded yet, we'll have to defer constructing this dictionary until it's used.
return false;
}
_SourceList = _SourceList.Copy(); //Copy the table so if the base table is modified externally we aren't affected.
_ListItems = _SourceList.DefaultView;
_IndexedView = CreateIndexedView(_SourceList, indexKeyField);
cachedViews = true;
}
ListItems = _ListItems;
IndexedView = _IndexedView;
return true;
}
private DataView CreateIndexedView(DataTable table, string indexKey)
{
// Create a data view sorted by ID ( keyField ) to quickly find a row.
DataView dataView = new DataView(table);
dataView.Sort = indexKey;
dataView.ApplyDefaultSort = true;
return dataView;
}
#endregion Private Methods
#region Static Event Handlers
private static void OnKeyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// When the Key changes, try to find the data row that has the new key.
// If it is not found, return null.
DataTableDictionary<T> dataTableDictionary = sender as DataTableDictionary<T>;
if( dataTableDictionary.m_isBeingChanged ) return; //Avoid Reentry
dataTableDictionary.m_isBeingChanged = true;
try
{
if( dataTableDictionary.IndexedView == null ) //We had to defer loading.
if( !dataTableDictionary.ConstructDictionary() )
return; //throw new Exception("Dataview is null. Check to make sure that all Reference tables are loaded.");
DataRowView[] result = _IndexedView.FindRows(dataTableDictionary.Key);
DataRowView dataRow = result.Length > 0 ? result[0] : null;
//Sometimes a null key is valid - but sometimes it's just xceed being dumb - so we only skip the following step if it wasn't xceed.
if( dataRow == null && dataTableDictionary.Key != null )
{
//The entry was not in the DataView, so we will add it to the underlying table so that it is not nullified. Treaty validation will take care of notifying the user.
DataRow newRow = _SourceList.NewRow();
//DataRowView newRow = _IndexedView.AddNew();
int keyIndex = _SourceList.Columns.IndexOf(dataTableDictionary.indexKeyField);
for( int i = 0; i < _SourceList.Columns.Count; i++ )
{
if( i == keyIndex )
{
newRow[i] = dataTableDictionary.Key;
}
else if( _SourceList.Columns[i].DataType == typeof(String) )
{
newRow[i] = "(Unrecognized Code: '" + (dataTableDictionary.Key == null ? "NULL" : dataTableDictionary.Key) + "')";
}
}
newRow.EndEdit();
_SourceList.Rows.InsertAt(newRow, 0);
dataRow = _IndexedView.FindRows(dataTableDictionary.Key)[0];
}
dataTableDictionary.Row = dataRow;
}
catch (Exception ex)
{
throw new Exception("Unknow error in DataTableDictionary.OnKeyChanged.", ex);
}
finally
{
dataTableDictionary.m_isBeingChanged = false;
}
}
private static void OnRowChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// When the Key changes, try to find the data row that has the new key.
// If it is not found, return null.
DataTableDictionary<T> dataTableDictionary = sender as DataTableDictionary<T>;
if( dataTableDictionary.m_isBeingChanged ) return; //Avoid Reentry
dataTableDictionary.m_isBeingChanged = true;
try
{
if( dataTableDictionary.Row == null )
{
dataTableDictionary.Key = null;
}
else
{
dataTableDictionary.Key = dataTableDictionary.Row[dataTableDictionary.indexKeyField];
}
}
catch (Exception ex)
{
throw new Exception("Unknow error in DataTableDictionary.OnRowChanged.", ex);
}
finally
{
dataTableDictionary.m_isBeingChanged = false;
}
}
#endregion Static Event Handlers
}