Creating a dynamic seach using MvvM and WPF - 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.

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 ?

How Bind Child Control To A View Object (Like PageCollectionView) In Master\Detail scenario With MVVM In SILVERLIGHT

I have a question about “Master\Detail” data binding using MVVM and I hope you can help me with it:
Let say my Master ModelView is “ProductModelView” (for Product Entity) and the Product Entity has a property named ProductInventories of type Entity Collection. I defined a “DomainCollectionView” in my master ProductModelView with name: “ProductViewDCV”
I do following items to fill my master and details Datagrids:
1-Set the DataContext of my page to my ModelView : ProductModelView
2-Bind master Datagrid (name: masterDG) to ProductViewDCV.
3-Bind detail Datagrid (name: detailDG) to ProductInventories property of my master ProductViewDCV :
<sdk:DataGrid x:Name=" detailDG "
ItemsSource="{Binding Path= ProductViewDCV. ProductInventories }"/>
The Program works well ,As I change the row in master DataGrid , The current item in the child DataGrid also changes.
But my question and Problem :
When I bind my detail (child) DataGrid as mentioned in step 3 , the child DataGrid is bound to “EntityCollection” not a View Object Like “PagedCollectionView” or “DomainCollectionView” ,So I can’t use benefits like Sorting ,Grouping and Paging on my child (detail) DataGrid. I want to know :How I can bind my child DataGrid to a View Object in MVVM pattern to use Paging option.(Namely I want to have Paging and Grouping Options in my child DataGrid.)
Look forward to receiving your reply.
Hello.
I really appreciate your kind and quick answer. I did as you said ,but I encounter a problem and I hope you help me:
Method:
In my “ProductModelView” (for Product Entity) I defined following objects:
1-private DomainCollectionView _ProductViewDCV; (As Master(Parent) View)
2-private PagedCollectionView _ProductViewPCV; (As Detail(Child) View)
3-In Constructor of “ProductModelView” class, I assigned an EVentHandler to “_ ProductViewDCV”(my DomainCollectionView as Master view):
this._ProductViewDCV.CurrentChanged+=_ProductViewDCV_CurrentChanged;
this._ProductViewDCV.Refresh();
4-In the EventHandler , I Create a new “PagedCollectionView” (my Detail/Child view) and assign it to my _ProductViewDCV :
if (_ProductViewDCV.CurrentItem != null)
{
_ProductViewPCV = new PagedCollectionView((_ProductViewDCV.CurrentItem as BA1.Web.Product).ProductInventories);
//ProductViewPCV.Refersh();
}
5-In my View File (XAML file), I have two Datagrids which are configured as follow :
Page DataContext is set to an object of my ModelView : ProductViewModel
sdk:DataGrid x:Name="dgParent" Grid.Row="1" ItemsSource="{Binding Path=ProductViewDCV}"//my Master/Parent DataGrid
sdk:DataGrid Grid.Row="2" x:Name="dgDetails" ItemsSource="{Binding Path=ProductViewPCV}//My Detail/Child DataGrid
Problem :
When I change the current row in my Master/Parent DataGrid , nothing happens in Detail/Child DataGrid and testing the Detail/Child DataGrid’s Itemssource property , returns “Null” ,not an object of “PagedCollectionview”.
Where’s my problem and why doesn’t the Detail/Child DataGrid get updated in response to Master/Parent current item changing? (Testing shows that ProductViewPCV itself changes when it’s parent collection changes.)
Again let me thank you for giving me advices on this problem.
Off the top of my head, there's a few problems I can see. You should instantiate the _ProductViewPCV PagedCollectionView property in the constructor, and not create new instances of it in the _ProductViewDCV_CurrentChanged event handler. If you did want to do this, then you need to notify the view that the value of this _ProductViewPCV property has changed by raising a PropertyChanged event. However, you shouldn't do this as it's likely to result in a memory leak.
You'd be best off wrapping an observable collection in the PagedCollectionView, and simply updating the contents of the observable collection in the _ProductViewDCV_CurrentChanged event handler I think. The view should update automatically I think. Sorry, I'm a little rusty on this, but I think that should work for you.

DataGrid relocate add new row to the top

In a DataGrid view, the blank row add entries is convenient but quickly get's lost when the list is large. Can you change it's default location from the bottom to the top of the DataGrid view?
I've rarely used a DataGrid and know nothing about its ability to add rows, but coming from a WPF/MVVM point of view, you don't need that anyway. Generally in WPF/MVVM, we manipulate data rather than UI controls, so the solution is easy. First we Bind our collection of data (in whatever shape we chose) to the DataGrid.ItemsSource property:
public ObservableCollection<DataItem> SomeDataCollection { get; set; }
...
<DataGrid ItemsSource="{Binding SomeDataCollection}" ... />
Now, if we want to add a new item to the bottom of the DataGrid we can do this in the code behind/view model:
SomeDataCollection.Add(new DataItem());
Then if we want to add a new item to the start of the collection, we can just do this:
SomeDataCollection.Insert(0, new DataItem());
Of course, you'll need to implement the INotifyPropertyChanged interface in your code behind/view model to make this work, but (hopefully) you'll be doing that anyway.
UPDATE >>>
Sorry, I misunderstood you. I found a NewItemPlaceholderPosition Enumeration on MSDN that is used by an ItemCollection.IEditableCollectionView.NewItemPlaceholderPosition Property. Unfortunately, those linked pages don't have an code examples, so I found one in the in wpf datagrid how to get the blank row on top? post here on StackOverflow. From the answer by #woodyiii in that post:
var view = CollectionView.GetDefaultCollectionView(EmployeeList)
as IEditableCollectionView;
if(view!=null)
view.NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtBeginning;
This does mean that you'd have to use a CollectionView to get this to work, but it seems like the only way... the NewItemPlaceholderPosition Enumeration isn't used by anything apart from the various CollectionView classes.
This worked for me:
IEditableCollectionView cv = (IEditableCollectionView)grdSchedule.Items;
cv.NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtBeginning;
Where grdSchedule is the the name of your DataGrid

How to bind to a method on ViewModel with data from View

I currently have one view with 3 fairly simplistic view models. For the sake of this discussion, we will focus on 2 of the three view models.
The View is a User Management user control. It contains a DataGrid that has its ItemsSource binding set to a UserListViewModel. This view model simply displays user information in the data grid.
The User Management View also contains some other controls, such as buttons for adding new users and removing users. Those buttons are currently bound to a second view model called UserManagementViewModel. For example, the Remove button will successfully call the RemoveUser method on the UserManagementViewModel.
My question is, via XAML (as I hate code-behind), how can I pass the SelectedItem property of the DataGrid (bound to UserListViewModel) into the RemoveUser method call on the UserManagementViewModel? I realize that, in the MVVM design pattern, my view model can't look into the view to retrieve the information necessary, so there must be a way via binding to pass that information into the method.
XAML code examples (or links that show how) to perform similar functionality would be appreciated. Thanks for any help!
you can simply use a commandparameter
<Button Command="{Binding RemoveCommand} CommandParameter="{Binding Elementname=gridUser, Path=SelectedItem}" />
or your UserManagementViewModel have access to the UserListViewModel then you need a command without commandparameter and simply use the SelectedUser property of your UserListViewModel instance
public void ExecuteRemove()
{
var userToRemove = this._myUserListViewModelinstance.SelectedUser;
...
}
I believe what you seek is commanding with a command target bound to the datagrid's selecteditem where one can route such information from the datagrid; say when a button is pressed.
See Commanding Overview on MSDN

How to Validate binded WPF TextBoxes which is depend on each other's value and how to enable/disable Ok button according to Validation

I have two textboxes and two buttons on c# WPF windows application
Application have One variable that called Total Amount.
one text box is for Discount Percentage and other one is for Discount Amount.
if i changed discount amount then percentage should get reflected using DataBinding in WPF and viceversa(I Have that)
I want to validate both the textboxes
1). Discount should be in range MIN to MAX
2). Discount Amount should not be grater than Total Amount
and then Ok button will get Enable/disable according to Validation
I would recommend that you adopt the MVVM pattern (if you haven't already) and have your validation logic contained in the view model. Using this approach, you can either:
Expose an IsValid property on the view model and bind it to the button; the property getter would return the result of your validation logic
Expose an ICommand on the view model whose CanExecute method would return the result of your validation logic (recommended)
A quick example:
public class DiscountViewModel : ViewModel // Base class implements INotifyPropertyChanged
{
// Define all of your view model properties, i.e., DiscountAmount, DiscountPercent, etc.
...
// Define a command
public ICommand OKCommand { get; }
}
Then in your XAML view, you would add the following binding:
<Button Command={Binding Path=OkCommand} Content="OK" />
Again, this is just a brief example that should help point you in the right direction. There are tons of great resources on the MVVM pattern available as well as resources for the WPF command pattern. Here is a good introductory resource that covers both: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
For every Binding you define you can add a Converter. This can converter can be a single value converter, then you must implement the IValueConverter Interface.
But I think you can solve your problem with a IMultiValueConverter. This Converter can be given much values, which can also get from Bindings. The Converter is getting the values from the Bindings, processing them with your logic and then giving it back to your property where you defined it.
http://msdn.microsoft.com/en-us/library/system.windows.data.imultivalueconverter.aspx

Resources