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

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

Related

Setting SelectedItem of Listbox or ComboBox through ViewModel MVVM WPF

Objective: having bound the SelectedItem of a ListBox (or ComboBox) to an instance of an object through xaml, I would like to set the selected instance of the object through the view model and have it reflect on the ListBox or ComboBox.
<ComboBox x:Name="cboServers" HorizontalAlignment="Left" Margin="535,694,0,0" VerticalAlignment="Top" Width="225"
ItemsSource="{Binding Settings.Servers}"
SelectedItem="{Binding Settings.SelectedServer, Mode=TwoWay}"
DisplayMemberPath="UserFriendlyName">
C# Model View code
public ObservableCollection<AutoSyncServer> Servers { get; set; }
private AutoSyncServer _selectedServer;
public AutoSyncServer SelectedServer
{
get { return _selectedServer;}
set
{
_selectedServer = value;
OnPropertyChanged("SelectedServer");
}
}
The list or combo box populates correctly. Selecting an item on the ListBox or ComboBox will correctly set the SelectedServer object.
However, if I try to write a set statement in C# such as:
Servers.Add(newServer);
SelectedServer = newServer;
The ListBox or ComboBox will correctly add the item and the SelectedServer object will be correctly set on the MVVM model, but the front end will not reflect this selection.
In this specific case, an xml file is read saying what the user had selected last, and when the window opens the ComboBox has nothing selected (the servers are all loaded correctly within it though)
What's missing here?
The actual object in SelectedItem must be an object instance which is found in the Servers collection, in the Object.ReferenceEquals(a, b) sense. Not just the same Name and ID (or whatever) properties; the same exact class instance.
The classic case where people run afoul of this is deserializing equivalent items in multiple places. Servers has a collection of deserialized AutoSyncServer instances, and Settings.SelectedServer is a separately deserialized AutoSyncServer instance, which has identical property values to one of the items in Servers. But it's still a different object, and the ComboBox has no way of knowing that you intend otherwise.
You could override AutoSyncServer.Equals() to return true if the two instances of AutoSyncServer are logically equivalent. I don't like doing that because it changes the semantics of the = operator for that class, which has bitten me before. But it's an option.
Another option is to have one canonical static collection of AutoSyncServer and make sure every class gets its instances from that.
I don't understand why this code didn't work, given the above:
Servers.Add(newServer);
SelectedServer = newServer;
Once newServer is in Servers, it should be selectable. I tested that and it's working for me as you would expect.
i think you must avoid "sub-bindings", they work once when the view ask for, but not well after
Settings.SelectedServer ==> SelectedServer
and if you comment OnServerChanged?.Invoke(this, _selectedServer); what is happening ? it works ?

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.

SelectedItem is updating when ListCollectionView changes

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.

Databinding causing other functions on page not to work when filling datagrid

I have a datagrid in my View. The datagrid's itemssource property is bound as such
ItemsSource="{Binding}"
Addtionally in the codebehind I have set datacontext by doing the following:
DataContext = ProcedureDatabaseViewModel.Procedures();
The Procedures function in the viewmodel successfully outputs a list which the DataGrid successfully displays.
The issue now is that the datacontext of the entire page is now set to the above. The result of this is that other elements that interect with the VM no longer work. I.E. buttons with commands that are found in the VM. I have tried removing the setting of the datacontext in the code behind but cannot figure out how to populate the datagrid otherwise. Please note that when the DataContext is not set in the code behind the context is changed to the VM, I believe, and thus, the other elements begin working again. I have tried changing the Itemssource property to target the list of objects that I wish to populate the datagrid with but it hasnt worked.
The list is
List<procedure> Procedures
and it is in the ProdureDatabaseViewModel. I tried to target it as
ItemsSource="{Binding ProdureDatabaseViewModel.Procedures}"
But this has not worked either.
Can someone please advise me on the correct way to do this?
The cleanest way is to use ItemsSource to bind your Procedures collection to your DataGrid. For this to work, Procedures has to be a Property. To avoid further issues, use an ObservableCollection. It should look like this:
ObservableCollection<procedure> Procedures { get; set; }
Then you should be able to simply bind it via
ItemsSource="{Binding Procedures}"
I prefer to set the DataContext in the constructor of the view to the complete ViewModel (which I created for this special view).
So I do something like this in the constructor of the view:
public View(ProcedureDatabaseViewModel viewModel)
{
this.DataContext = viewModel;
}
This way everything else should still work and you can use more than just the procedures.
Next you bind the procedures to your datagrid:
ItemsSource="{Binding ProcedureList}"
Do note that for this to work, "Procedures" needs to be a property. It's not clear in your question if it is a function, a property or just a simple class member. If it is a function, you can do it like this in your view model:
public List<procedure> ProcedureList
{
get { return this.Procedures(); }
}
I have tried removing the setting of the datacontext in the code
behind but cannot figure out how to populate the datagrid otherwise.
You can set the DataContext of your DataGrid separately like so, MyDataGrid.DataContext = ProcedureDatabaseViewModel.Procedures();.
And apply separate DataContext for your Page.

WPF Toolkit datagrid /doesn’t refresh data

H I use SQL CE and LINQ. I bind property typeof Table on ItemSource of Datagrid control from WPF Toolkit.
Something like this.
public Table<TestNick> MySource
{
get { return _tab; }
set
{
_tab = value;
NotifyPropertyChanged("MySource");
}
}
<Controls:DataGrid Name="Dg"
ItemsSource="{Binding Path=MySource, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="0"/>
I retrieve data from DB with LINQ:
const string connStr = #"Spiri_SQL_CE_DB.sdf";
_dc = new Spiri_SQL_CE_DB(connStr);
MySource = _dc.TestNick;
If I add a breakpoint on last line I see all values from tables TestNick, but it doesn’t load this data in DataGrid.
What is bad?
EDITED:
I check the ItemSource of DataGrid control in code behind, the item source is correct but I see in DataGrid (view) "old data".
So binding must be correct, problem is that DataGrid control doesn’t refresh data.
Make sure datagrid autogeneratecolumns is true
While running check the output window if there are any binding issues
Another trick is to put a button on the view and write a code behind function on click of that button to debug whats the datagrid itemsource, if its empty try to invoke viewModel/Model's getDatagridData function and then see if it loads, in case it loads that means your NotifyPropertyChanged is not yet functional

Resources