SelectedItem is updating when ListCollectionView changes - wpf

I am using WPF, MVVM, and entity framework.
I am working with a data entry application, and I am trying to enable cancel changes to work in my app. Where when changes are cancelled, all values reset to their original value. I think I have everything on the EF side setup correctly. Basically I just set all entities to unchanged if they are in the modified list.
My problem is when I come back to the ViewModel, and I am trying to re-setup all of the fields and derived properties. The biggest annoyance has been the collections. We have multiple combo box controls that we bind to a ListCollectionView and then I have an additional property in the view model that represents the SelectedItem. When I am resetting up the collections I was just allowing the process to re-initiate all the properties including the collections. When I change the collection, it tries to also change the selected property. The problem with this is if it changes the selected property the backing entity gets updated with the new values (as if the user selected an item), and I technically can't get the value back.
I was actually having a reverse problem when I was saving. After the save the form would go into its not edit mode and the value would be set to the old value. Re-opening the form in edit would load the correct value. To fix this I added to the form IsSynchronizedWithCurrentItem=true. But now I am having the problem reverse problem where the value goes back to old value during edit.
// View Code
<ComboBox Grid.Row="1"
Grid.Column="2"
ItemTemplate="{StaticResource TransformerTypeDisplayDataTemplate}"
ItemsSource="{Binding Path=TransformerTypeCollection}"
SelectedItem="{Binding Path=SelectedTransformerType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource AssetViewStateAwareComboBox}" Margin="0,0,0,2" VerticalAlignment="Bottom" />
//ViewModel Properties
private ListCollectionView<TransformerType> _transformerTypeCollection;
public ListCollectionView<TransformerType> TransformerTypeCollection
{
get { return _transformerTypeCollection; }
set { _transformerTypeCollection = value; RaisePropertyChanged("TransformerTypeCollection"); }
}
private TransformerType _selectedTransformerType;
public TransformerType SelectedTransformerType
{
get
{
return _selectedTransformerType;
}
set
{
_selectedTransformerType = value;
if (IsInEditMode)
{
BackingEntity.TransformerTypeID = _selectedTransformerType.ID;
BackingEntity.TransformerType = _selectedTransformerType;
}
RaisePropertyChanged("SelectedTransformerType");
}
}
// Setting the collection will trigger the set method for SelectedTransformerType
TransformerTypeCollection = TaskCoordinator.TransformerTypes.GetView();
My current work around for this problem is I keep around a state variable that says the collections have already been populated. And it skips the resetting the collections on the re-setup of the view model.

Related

When does a ComboBox receive its Items if it is bound to ObservableCollection?

I am attempting a save/load mechanism for re-use in a business application. I have the groundwork laid to read/write ObservableCollection<> to/from xml, using attributes to describe my class properties. That part is working. I can save an ObservableCollection to XML, then load the XML back into an ObservableCollection the next time I run the program.
Here's my problem. I have a ComboBox whose ItemsSource.DataContext = ObservableCollection<Flag>;
When I run the program, it accepts the binding just fine, but the ComboBox itself does not populate itself until later. I want to set the SelectedItem to be the first item in the ObservableCollection<Flag> that I have loaded from XML. Nothing happens though, because as the program is executing it's startup methods, the Items.Count remains 0. I'm guessing the ComboBox doesn't populate itself until it gets focus. How do I work around this? Can I force the ComboBox to populate itself? I've tried cb_ARDAR_ARFlag.Items.Refresh();
XAML:
<ComboBox Name="cb_ARDAR_ARFlag"
ItemsSource="{Binding}"
SelectionChanged="cb_ARDAR_ARFlag_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Flag_Desc}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Relevant Code:
public MainWindow()
{
InitializeComponent();
setDataBinding();
loadSavedData();
}
private void setDataBinding()
{
//Returns ObservableCollection<Flag>
cb_ARDAR_ARFlag.DataContext = Flag.getOCAvailableFlags();
}
private void loadSavedData()
{
//When it gets here the ItemCount is 0 so nothing happens.
//Refresh didn't help
cb_ARDAR_ARFlag.Items.Refresh();
Flag f = Enforcement_Save.loadOCARFlag().First();
cb_ARDAR_ARFlag.SelectedItem = f;
}
At this point I'm still not sure the code at the end will successfully identify the correct 'flag' item to be selected, or if I'll end up using Linq. Which, by the way, leads me to another question. Can you Linq to ComboBox.Items somehow?
I have recreated your issue, and your are correct, the items count is = 0 in the loadSavedData method. The combobox doesn't seem to be populated until after the constructor has fully executed.
In the meantime I found you can use the ItemsSource property to load the combobox at the time you want it loaded:
cb_ARDAR_ARFlag.ItemsSource = Flag.getOCAvailableFlags();

WPF DataGrid automatically updates in-memory data?

I'm using WPF and MVVM pattern to develop a desktop application. Maybe I'm not clear about how a DataGrid control would work, but if I modify an item (text, checkbox, etc.), the modification persists even if I don't make any permanent database update (using Entity Framework). For example, I may switch to view different data, and when I come back to view the grid with modified data (but without saving to db), the change is there. Somehow the in-memory data has been changed by the DataGrid control and is not refreshed or synced with database.
In other words, the data in the DataGrid remained modified until I stop and re-run it from visual studio.
UPDATED:
Another way to ask this question would be: What actually happens when I update, say, an item of a DataGrid? If it is bound to a ViewModel's property P in two-way mode then I suppose P will be updated. But even if I refresh its value (setting the P to null then calling the data access methods again), the modified data are still there.
Does anybody have any idea of what happened?
Thanks!
UPDATED 2:
Here is the xaml code which binds a DataGrid to a property named UserList in the ViewModel.
<DataGrid
x:Name="UserList"
ItemsSource="{Binding UserList, Mode=TwoWay}"
AutoGenerateColumns="False"
AllowDrop="True"
RowBackground="Orange"
AlternatingRowBackground="#FFC4B0B0">
<!-- define columns to view -->
<DataGrid.Columns>
...
</DataGrid.Columns>
</DataGrid>
Here is the code running in the ViewModel. The method InitialiseData() is called in the constructor of the VM and before I want to do something else with persistent data, so I supposed is always refreshed.
private void InitialiseData()
{
// Retrieves user list from the business layer's response
Response userList = _userBL.GetUserList();
if (userList is FailResponse)
{
MessageBox.Show(userList.Message);
return;
}
else
{
UserList = null;
UserList = (IEnumerable<User>)((SuccessResponse)userList).Data;
}
** UPDATED 3 **:
private IEnumerable<User> _userList;
public IEnumerable<User> UserList
{
get
{
return _userList;
}
set
{
_userList = value;
NotifyOfPropertyChange(() => UserList);
}
}
If you switch back, you are switching to in-memory collection which was updated by DataGrid before. Or do you load data from dtb again?
EDIT:
Ok, now as you have posted the code, I know where is the problem.
DataGrid is not refreshed as I thought. Make sure, you will raise NotifyProperyChanged on the property UserList. Then it will work. See ObservableCollection class as well.

Combo-box loses selection after collection changes

I have a WPF combo box bound to an obvserable collection (OC):
<ComboBox Name="cbCombination" ItemsSource="{Binding Combinations}"
SelectedIndex="0" />
Elsewhere, in the object set as data context:
public ObservableCollection<Combination> Combinations { get; set; }
Combination overrides its ToString and everything is peachy: The combo-box's drop-down displays all Combination items in the Combinations OC. The combo-box's selection box displays the value of the first Combination.
Now, the data-context object has to change the values in its Combinations OC:
var combinationsList = CombinationsManager.CombinationsFor(someParam);
this.Combinations.Clear();
foreach (var combination in combinationsList)
this.Combinations.Add(combination);
NotifyPropertyChanged(#"Combinations");
This causes the combo-box's selection box shows an empty string. (The drop-down is closed. However, when I make it drop down, it does show the correct new Combinations, so it is bound to the updated collection).
I tried to capture both SourceUpdated and (in my dispair) TargetUpdated events (thining of setting the SelectedIndex there), but my event handlers didn't get called!
So my question is: How do I make a WPF ComboBox refresh the value of its selection-box when the observable collection it is bound-to changes?
Update:
I've totally forgotten to mention, and I don't know whether it's important, but the combo-box is within a UserControl. The UserControl's XAML looks like this:
<UserControl x:Class="...CombinationsToolBar"
.... mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<ToolBarTray Name="toolBarTray1" >
<ToolBar Name="toolBar1">
<ComboBox Name="cbCombination"
ItemsSource="{Binding Path=Combinations, NotifyOnSourceUpdated=True,
IsAsync=True}"
SelectedIndex="0" IsEditable="False"
SelectionChanged="CbCombinationSelectionChanged"
SourceUpdated="CbCombinationSourceUpdated"
TargetUpdated="CbCombinationTargetUpdated"></ComboBox>
</ToolBar>
</ToolBarTray>
In the UserControl's code I have breakpoints on CbCombinationSelectionChanged, CbCombinationSourceUpdated and CbCombinationTargetUpdated.
The CbCombinationSelectionChanged fires once when the form containing the user control is first loaded. It is indeed called a second time when the Combinations collection is cleared, as #asktomsk said.
The source updated and target updated are not triggered - CbCombinationSourceUpdated and CbCombinationTargetUpdated are not called.
As the combo box is inside a usercontrol and the Combinations collection is within the view model, and as the view model doesn't have access to the combo box, I have no opportunity to set the selected index of the combo unless the events fire.
:(
The problem is in your this.Combinations.Clear();
When you do it, it sets SelectedItem = null and SelectedIndex = -1
So you should set selection again. If you have an access to your ComboBox then just write this cbCombination.SelectedIndex = 0; after filling Combinations list.
Of course you can bind SelectedItem/SelectedIndex to some property in code behind too.
BTW
Also it is not required to call NotifyPropertyChanged(#"Combinations"); after filling the collection because Combinations already implements INotifyPropertyChanged.
Update
To detect that your ObservableCollection was changed, subscribe to CollectionChanged event in your UserControl code behind. Make sure that you subscribed before collection changed!
Combinations.CollectionChanged += (s, e) =>
{
if (cbCombination.Items.Count > 0)
cbCombination.SelectedIndex = 0;
};
Another suggestion
Approach above is works when you do not need a smarter logic than just select zero index in combobox.
But often you will need a more complex logic to select some item. In this case you may add new property to your model:
public Combination SelectedCombination
{
get{ return _selectedCombination; }
set
{
_selectedCombination = value;
NotifyPropertyChanged("SelectedCombination");
}
}
and bind this property to your combobox:
<ComboBox Name="cbCombination" ItemsSource="{Binding Combinations}"
SelectedIndex="0" SelectedItem={Bindings SelectedCombination} />
In this case you can select any item when filling the Combinations collection and it will be automatically selected in combobox:
var combinationsList = CombinationsManager.CombinationsFor(someParam);
this.Combinations.Clear();
foreach (var combination in combinationsList)
this.Combinations.Add(combination);
if (Combinations.Count > 0)
SelectedCombination = Combinations[0];

WPF ComboBox MVVM strange behavior

I'm having a strange behavior when I associate and combobox to my viewmodel. The behavior is the following, when I change the selected value of the combo I do a validation of the new value and if this new value is invalid I keep the old value and discard the new one, in this way I don't raise the Inotifypropertychanged, but the getter from the property associated to the combobox is call anyway, this gets the old value that I want to show, but instead the combobox shows the new value, even though the selectedvalue of the combo as the old and correct value, I checked in debug mode. I don't know how can I solve this because I never saw this kind of behavior, any suggestions would be much appreciated.
This is the code of the XAML
<ComboBox Height="23" Name="cbxStatus" HorizontalAlignment="Left"
ItemsSource="{Binding Path=Status, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=SelectedStatus, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Value" Width="130" VerticalAlignment="Center"
IsEnabled="{Binding Path=StatusEnable, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
this is the viewmodel code, the property
public Config SelectedStatus
{
get
{
if (ApplicationAction == ApplicationAction.Add)
{
base.Object.State = configManager.BudgetInitStatus();
StatusEnable = false;
}
else
{
StatusEnable = true;
}
return base.Object.State;
}
set
{
if (base.Service.CanChangeBudgetStatus(base.Object, value))
{
base.Object.State = value;
base.Object.IsDirty = true;
}
RaiseOnPropertyChanged("SelectedStatus");
RaiseOnPropertyChanged("AssociateOrderButtonVisibility");
}
}
Thanks for the help
As indicated in my comment to Jay, the problem here is that WPF is setting the value and not listening to your change notification (which it is, after all, expecting). What you need to do is raise the property change notification outside the context of the current message. You could do this using the dispatcher, for example:
set
{
if (!valid)
{
// value is unchanged
Dispatcher.BeginInvoke(delegate { this.OnPropertyChanged(...) });
return;
}
// value is changed here
}
This will ensure the current data binding message is executed, then a separate message tells WPF that, "actually, the value you just provided to my setter is no longer the current value".
You could also use SynchronizationContext if you prefer. Either way, I admit it's a little hacky. Unfortunately, I don't know of a nice way around this. The fact is, WPF assumes that the value it passes to your setter is the effective value of the property. No amount of property change notifications within the context of the binding operation will convince it otherwise.
Setting your binding to
IsAsync=True
will work.
When the user changes the value in the combobox, it gets changed in the combobox, irrespective of your viewmodel.
If you change that value back and do not raise a property change notification, your viewmodel and view will be out of sync.
In short, when you reject the selected value you still need the property change notification.

WPF ComboBox which updates its ItemsSource from the database as the Text property changes

I would like to have a ComboBox control on a form which will be used to search a list of investments as the user types. I can do this easily if I cache the entire collection of investments from the database on startup (currently 3,000 or so items), but I would prefer to not do that if it isn't necessary.
The behavior that I am trying to implement is:
The user types text into the editable ComboBox.
As the user enters each character, the database search function is triggered, narrowing down the search results with each successive keystroke.
As the search results are updated, the dropdown panel opens and displays the relevant matches
I have tried binding the Text property of the ComboBox to the InvestmentName (string) property on my ViewModel, and the ItemsSource property of the ComboBox to the InvestmentList (generic List) property on my ViewModel. When I do this, the Text property auto-completes from the ItemsSource, however the dropdown appears empty.
I have been able to achieve these results using a TextBox stacked on top of a ListBox, but it isn't very elegant and it takes up more screen real estate. I've also been able to get it to work with a TextBox stacked on top of a ComboBox, although the ComboBox steals the focus when the IsDropDownOpen property is set to "true" when there are valid search items. It also isn't very visually pleasing to use two controls for this.
I feel like I'm really close to getting it to work the way I want it to, but there is something which eludes me.
The XAML for this control is:
<ComboBox Height="23" Width="260" IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left"
ItemsSource="{Binding InvestmentList}" DisplayMemberPath="FullName"
IsDropDownOpen="{Binding DoShowInvestmentList}"
ItemsPanel="{DynamicResource ItemsTemplate}" IsEditable="True"
Text="{Binding Path=InvestmentName, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
The relevant ViewModel properties are:
private bool _doShowInvestmentList;
public bool DoShowInvestmentList
{
get { return _doShowInvestmentList; }
set { if (_doShowInvestmentList != value) { _doShowInvestmentList = value; RaisePropertyChanged("DoShowInvestmentList"); } }
}
private List<PFInvestment> _investmentList;
public List<PFInvestment> InvestmentList
{
get { return _investmentList; }
set { if (_investmentList != value) { _investmentList = value; RaisePropertyChanged("InvestmentList"); } }
}
private string _investmentName;
public string InvestmentName
{
get { return _investmentName; }
set
{
if (_investmentName != value)
{
_investmentName = value;
this.InvestmentList = DataAccess.SearchInvestmentsByName(value).ToList();
if (this.InvestmentList != null && this.InvestmentList.Count > 0)
this.DoShowInvestmentList = true;
else
this.DoShowInvestmentList = false;
RaisePropertyChanged("InvestmentName");
}
}
}
I've done a fair bit of research on this, but I haven't quite found the answer yet.
Check out this great article on CodeProject by... me :)
A Reusable WPF Autocomplete TextBox
Look towards the end for the Google suggest example, it is similar to what you need, where every keypress triggers another query to the server.

Resources