Looping through (and removing) items of a bound ComboBox - wpf

I am new to WPF - and painfully aware of it. I have unsuccessfully searched for answers to this particular problem and am now seeking advice from my more knowledgeable peers!
The scenario
The application on which I am working allows users to either enter new records into a database, or amend existing ones.
I have a form containing a bound ComboBox. It is populated from the database, which is accessed by a WPF service that exposes a DTO.
From the UI perspective, the form has two modes:
1. enter new record
2. amend existing record
The ComboBox in question appears in both cases, but the requirement is to have fewer options visible when the form is in 'amend' mode.
What I am trying to do is loop through the ComboBox items when the form is in 'amend' mode and remove/hide the options that should not appear.
The XAML
<ComboBox x:Name="RecordType" Grid.Column="1" Grid.Row="1" Width="150" HorizontalAlignment="Left" SelectedValue="{Binding Path=RecordTypeID,TargetNullValue=0}"/>
The code behind - and my (feeble!) attempts so far
foreach (ComboBoxItem item in this.RecordType.Items)
{
if (IsApplicable(item.Content.ToString()) == false)
{
item.Visibility = Visibility.Hidden;
}
}
(NOTE: IsApplicable() is a simple method that compares the string it receives to a list of the options that are allowed to appear when the form is in 'amend' mode.)
The problem
As I'm sure many of you will already know ... cannot cast object of type DTO to type System.Windows.Controls.ComboBoxItem
The question(s)
Can I get at the string values in this, or a similar way? If so, how, please?

Correct way to do this would be to apply Filter on Collection View
See Automatically Filtering a ComboBox in WPF
ICollectionView view = CollectionViewSource.GetDefaultView(comboBox.ItemsSource);
view.Filter = IsApplicable
view.Refresh(); // <-- call this whenever you change the view model

It would likely be easier for you if you bound your combobox to an ObservableCollection and then removed the items from the collection when needed.
Here is an example: http://www.tanguay.info/web/index.php?pg=codeExamples&id=304

Related

WPF Datagrid bound to ViewModel.DataTable. SelectedItem not binding to ViewModel.SelectedObject

TYFYT. I need some help understanding what's going on here and how binding to a datagrid works. I am coming from a knowledge base of working with WinForms so maybe my approach is all wrong?...
I have a WPF window, with a Datagrid.
In the ViewModel I have DataTable that I have populated with my Booking objects. The cols across the top correspond to a TimeSlot, and the row headers correspond to an instructor.
void DisplayBookings()
{
foreach (Booking booking in bookings)
{ //Add each Booking object to the DataTable
DtBookings.Rows[booking.GridRow][booking.SlotTimeId + 1] = booking;
}
}
In the window xaml I have the datagrid bound to the DtBookings.
<DataGrid x:Name="grdInstructors"
ItemsSource="{Binding DtBookings}" DisplayMemberPath="Id"
SelectedItem="{Binding SelectedCell}"
When I run this, it's working fine. I see the bookings in the correct cells in the grid.
Trying to do work against the grid is what is raising questions.
Q1. What is the grid cell holding? I have put the Booking objects in the ViewModel.DataTable and in the grid cell I see "myNamespace.Models.Booking". This is fine but when I try to access it I get the error "'Unable to cast object of type 'System.String' to type 'MyObject' (Booking)
So is the datagrid only holding a String that is the name of the object? I could accept that answer since I'm thinking the View should only be a visual representation of the objects.
If that is so, then how should SelectedItem=ViewModel.MySelectedItem work? (In this case it doesn't). Initially I thought it should have a copy of the object so that when I click on the cell it will tell the ViewModel what object I have chosen. This normally works when I am bound to an ObservableCollection, but in this case it is bound to a DataTable so I'm not getting the same result.
And on a final note I have noticed that DisplayMemberPath="Id" doesn't work in this case. (It's not really an issue since I usually override `ToString())
It would be great to have a definitive answer on this before I add logic to work around the problem and defeat the point of MVVM and Binding.
TYVM.

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 does not show records unless its ItemsSource property explicitly bound to the list of records

I have the following issue in my WPF application. I have a dedicated area in my Main window where I want to display various user controls, depending on the user's input. One of the UCs I have will allow the user to search the repository for some records. The customer's request is that the UC should contain all records when displayed the first time and only filter the data based on search criteria for subsequent searches. I am using the MVVM patter and I have my views, my models (defined in a separate Business Layer project) and my view-models.
I inject the repository service in my UC's view model, so I can fetch the data any time the user performs a search.
I set the the DataContext of my view to the instance of the view-model I created. Here is an excerpt from the Initialize method of my MainWindowviewModel class:
_myUserControlView = new MyUserControlView();
var userControlViewModel = new MyUserControlViewModel(_repositoryService);
_myUserControlView.DataContext = userControlViewModel;
The user control view contains a grid that is supposed to display the records fetched by the repo service. When the view-model for my UC view gets built, I can see that the repo service is fetching the correct data. But nothing is displayed in the grid. The XAML contains the proper ItemSource binding for the grid:
<DataGrid Name="MyUcDataGrid" AutoGenerateColumns="True" Height="Auto" Width="Auto" ItemsSource="{Binding MyRepoFetchedList}">
However, if I explicitly set the ItemsSource in my Initialize method, the data is properly displayed:
_myUserControlView = new MyUserControlView();
var userControlViewModel = new MyUserControlViewModel(_repositoryService);
_myUserControlView.DataContext = userControlViewModel;
_myUserControlView.MyUcDataGrid.ItemsSource = userControlViewModel.MyRepoFetchedList;
I was following some courses on PluralSight and none of them had this explicit assignment. For all those working examples, the binding in the XAML was enough.
What am I missing? My models are defined in a BL library, but I do not think that is the case. The samples on PluralSight, more simpler than my solution, would have the models in the same WPF project. The other thing is that the example I saw on PluralSight would fetch the records asynchronously. My code, still in the early development stages, does not. I will change that, but I don't think it is the case. Anyone has any clues why the DataGrid would not display my records unless I bind again the ItemsSource to the proper list in the code?
TIA,
Eddie

Creating a dynamic seach using MvvM and WPF

I am fairly new to mvvm and WPF so I am looking for some pointers to achieve the following.
I would like to create a search section in my app that builds up the search options based on whatever the users selects as their criteria.
So for example the first combo box of the search offers the top level of choices:
Date of Message
Gateway
Direction
Etc..
......
The second section is my operator
So, for example if Date of Message is selected then the user is offered another combo box of choices
Is Between
Last Week
Last Month
Etc..
The last section of the search is based on the operator above, so if Is Between is selected the form displays two Date Pickers from and to. If on the other hand Last Week is selected then nothing is displayed as the search can directly call my SetActionLogsForLastWeek() method once I click my search button.
If the user selects Gateway from the initial list then another combo box is build with a list of choices based on gateways.
I am looking for a tutorial or previous post that points me in the right direction on achieving my goal of building WPF elements based on selection choices from other elements.
Thanks
This is a rather extensive question, and as such there is no definitive answer. I will describe how I would handle this problem. Keep in mind that this might not be the best solution out there.
In order to render the controls which depend on the first combo box use data templates and distinct view models. In the resource section of your main view, or an external resource if you have defined one, write something like this:
<DataTemplate DataType="{x:Type vm:YourViewModel}"> <vw:YourView /> </DataTemplate>
You will have to import the namespaces containing your viewmodels and views (vm: and vw: here). Using content-controls you can render views depending on their viewmodels:
<ContentControl Content="{Binding CurrentViewModel}"></ContentControl>
In your code behind, you will have to have a ViewModel which you can bind to. Depending on the selection of the first combo box, you can now swap out the ViewModel in the code behind in order to render the dependent combo boxes - or nothing at all!
In order to illustrate, let me provide some sample code. Lets start with you main view model. Thats the one containing the collection the first combo box binds to (e.g. the strings "Date of Message","Gateway"...)
public class MainViewModel : BaseViewModel {
public IObservableCollection comboBoxItems;
public BaseViewModel ControlViewModel {get; set;}
private String selectedItem;
public String SelectedItem {
get {
return selectedItem;
}
set {
selectedItem = value;
OnPropertyChanged("SelectedItem");
ChangeControls();
}
}
private void ChangeControls(){
switch(selectedItem):
//swap out the control view model here e.g.
controlViewModel = new GateWayControlViewModel();
}
}
Btw, BaseViewModel is an abstract class which all view models inherit from. It implements INotifyPropertyChanged.
In your main view it is enough to have content control binding to the control view model, as well as the first combo box:
In your resources.xaml you define the data template, as discussed before.
<DataTemplate DataType="{x:Type vw:GatewayControlViewModel}"> <vw:GatewayView /> </DataTemplate>
This will render the GateWayView if the ViewModel, which the ContentControl in the main view is bound to is equal to GateWayControlViewModel. Finally, your GatewayView could look like this:
<ComboBox ItemsSource="{Binding Gateways}"
SelectedValue="{Binding SelectedGateway}" />
Obviously, you will have to make sure your GatewayControlViewModel actually contains the items and properties mentioned in the view. It will also need to derive from BaseViewModel.
I hope this gives you some ideas. Additionally, you might find this link useful (as it contains some very useful tips on how to approach MVVM).
If you want to implement Search in Collections for example in as in Combobox or Grid you should read a bit about ICollectionView. The ICollectionView will allow you to filter collection based on your search criteria. In your example you can also do this by handling selection changed events at the viewmodel and create method to update your data based on selected inputs.

WPF databinding to System.Data.Linq.Table<T>, how to update the interface?

I have this combo bound to a linq table.
Is there a lightweight way to update the UI (combo values that are displayed to the user) when I insert a new record in the linq table?
Basically from what I understand I should have used an ObservableCollection, but I don't want to copy the data back & forth from the linq table to that collection, I only want to have data in the linq table.
Is that possible?
EDIT
OK Here is what i have done (and still it doesn't work):
private ObservableCollection<Categories> m_Categories;
private ObservableCollection<Categories> Categories
{
get
{
return m_Categories;
}
}
in the XAML i have:
<ComboBox Name="cmbCategory"
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
/>
So, pretty simple.
//if i have a new category, i want to update the combo's content
if (frmEditCategory.ShowDialog() == true)
{
//get the new category and add it to the ObservableCollection
LibraryDataStore.Instance.Categories.ToList().ForEach(p =>
{
if (!m_Categories.Contains(p))
{
m_Categories.Add(p);
}
});
//update the target? is this correct?!
BindingExpression be = cmbCategory.GetBindingExpression(ComboBox.ItemsSourceProperty);
if (be != null)
be.UpdateTarget();
}
Checked with the debugger, m_Categories contains the new category, but it doesn't show up in the combo.
Also, do you know any good tutorial or blog post about combo binding?...
Thank you in advance
What you are asking for is not possible. You need a mediator of some sort, and you have already identified the correct one- ObservableCollection. You have to move the Linq data somewhere if you want your user interface to be notified when it changes. You should have one ObservableCollection, which you add your Linq data to, and then you should bind the combo-box to the ObservableCollection rather than directly to the table. That is the simplest, most light-weight solution you can create and still receive automatic change notifications.

Resources