Navigation between pages with model refresh - wpf

Question at the end
Information : I use Modern UI but I think that this problem might not be related only to this framework, hence I posted it under 'wpf' and 'data-binding'.
What am I trying to accomplish:
I have datagrid with some simple data in it (let's call this data products). I want to create a following feature.
After double click on row user is being redirected to a 'product edition' page (let's call this page ProductPage.xaml) where there will be more information about product. Before showing all data to user application is supposted to call SQL Server and ask for all info about selected product. Later on user can see all info about product where he can edit it. User accepts editions by clicking on the button, if button wasn't clicked but if he go to other page without clicking this button edits should be disregarded.
Problem
After a redirection to ProductPage.xaml with basic info about product hidden in Application.Current.Properties this page is not being refreshed.
Constructor is not being called (as I suspect this is expected behavior) hence I created method which is being called after page is loaded however even if model is being refreshed page is not. All bindings do work correctly during first page load (when constructor is being called) but when I go back to datagrid and choose different row old model is present.
Code
How do I make a redirect? Firstly what I did was :
public void MouseDoubleClickMethod()
{
/// getting id by row Id = ...
var url = $"ProductPage.xaml?id={id}"
BBCodeBlock bs = new BBCodeBlock();
Application.Current.Properties["product_id"] = Id;
bs.LinkNavigator.Navigate(new Uri(url, UriKind.Relative), this);
///some code
}
My model simplified
public class Product : INotifyPropertyChanged{
private decimal _price;
public decimal Price
{
get { return _price; }
set
{
_price = value;
OnPropertyChanged(nameof(Price));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My binding simplified (Product is a public property)
<StackPanel DataContext="{Binding RelativeSource={RelativeSource AncestorType=Page}}">
<StackPanel DataContext="{Binding Path=Product}">
<TextBox x:Name="Price" Text="{Binding Path=Price, Mode=TwoWay}" />
My after-load method simplified
productId = (int)Application.Current.Properties["product_id"];
Product.UpdateFrom(Database.GetGetProduct(id)); // this method updates all fields in model
Comments
Model DOES change but it it is textbox not refreshing.
Textbox keeps the same data regardless being binded to a property.
I use ?id={id} to make sure that other row is being loaded, without it it keeps showing first double clicked row's data.
I suspect my bindings to be faulty.
Question
What can I do with this binding? I want my textbox to refresh after page load not only when new object is being constructed. If you have no idea what to do with it just post some suggestions on how to debug it.
This is fixable by using GUID as parameter in url but I do not want to do that. Why? It forces to execute page constructor (hence rebuilding xaml).

If I recall correctly, MUI, when using it's navigation framework, it caches the data for the page. It doesn't construct the view (and subsequently your view model) each time it navigates. To support this, MUI has added navigation events for you to hook into to be notified when views are navigated to/from. in these events you could then update your data. From their wiki
Make your content navigation aware by implementing the IContent interface available in the FirstFloor.ModernUI.Windows namespace.
You can have your view model implement the interface and on load, refresh your data.

Related

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.

Why does not the view gets updated in binding?

I am fairly new to MVVM and wpf. I was implementing a simple login page that connects to the database to retrieve user details. In the view model i have a string field called Error that stores any error like wrong password or connection error to show up on view. I bound the error to a textblock in view. So on pressing the login button if some error happens i update the text of error. But the problem is the change is not reflected in view.
string _error;
public string Error
{
get { return _error; }
set { _error = value; }
}
update the field as
_error = "Wrong password!";
In the view:
<TextBlock Text="{Binding Error, UpdateSourceTrigger=PropertyChanged}"/>
What is the problem?
Edit: I read article on Code Project and am implementing it in similar way. Still it is not working?
Add the interface INotifyPropertyChanged to your ViewModel.
Accept the recommendation for ReSharper to implement the interface for you (you might have to install ReSharper).
In the setter for the property, add OnPropertyChanged("Error");.
Now, if you useError ="test";` it will run the setter, which runs the property notify changed, which pushes the change into the View so it can be seen.
You need to send out a property changed event from the ViewModel. The class needs to implement the INotifyPropertyChanged interface that contains the event you need to fire. The parameter of the event needs to be the name of the property you changed.

Prism, Unity, and Multiple Views ala MDI

I'm trying to create an application similar to Visual Studio in that we have a main content area (i.e. where documents are displayed in a TabControl, not a true MDI interface), with a menu on the side.
So far, I have everything working, except the content. My goal is that when a user double clicks on an item in the navigation menu on the side, it opens the document in the Content region. This works, but every time I double click it spawns a new instance of that same view. There's a chance that I could have multiple views of the same type (but different "names") in the TabControl content container.
Right now, my code looks something like this...
IRegion contentRegion = IRegionManager.Regions[RegionNames.ContentRegion];
object view = IUnityContainer.Resolve(viewModel.ViewType, viewModel.UniqueName);
if (!IUnityContainer.IsRegistered(viewModel.ViewType, viewModel.UniqueName))
{
IUnityContainer.RegisterInstance(viewModel.UniqueName, view);
contentRegion.Add(view);
}
contentRegion.Activate(view);
However, it appears that the view is never registered, even though I register it... I imagine I'm probably doing this wrong -- is there another way to do this? (re: the right way)
So, the problem was trying to do it this entire way. The smart method (for anyone else trying to do this) is to make use of Prism the correct way.
What I ended up doing was instead Navigating by:
1. In the Navigation Menu, constructing a UriQuery (included in Prism) with the UniqueID of the view I want to display (which is guaranteed to be unique) and adding that to the View I wanted to navigate to, i.e.:
IRegionManager.RequestNavigate(RegionNames.ContentRegion, new Uri(ViewNames.MyViewName + query.ToString(), UriKind.Relative));
where query is the UriQuery object.
2. Register the View and ViewName in the Module via:
IUnityContainer container = ServiceLocator.Current.GetInstance<IUnityContainer>();
container.RegisterType<object, MyView>(Infrastructure.ViewNames.MyViewName);
3. In the View, make sure the ViewModel is a parameter on the constructor. Let Prism inject this manually for us. Inside the constructor, make sure you set the DataContext to the incoming ViewModel.
4. Finally, make sure your ViewModel implements INavigationAware interface... This is a very simple implementation of it (UniqueID is a property on the ViewModel):
public virtual bool IsNavigationTarget(NavigationContext navigationContext)
{
if (navigationContext.Parameters != null)
return (navigationContext.Parameters["UniqueID"] == UniqueID);
return false;
}
public virtual void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public virtual void OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.Parameters != null)
UniqueID = navigationContext.Parameters["UniqueID"];
}
From here, Prism will ensure that only one view of your "UniqueID" will exists, while allowing for others of the same view, but different ViewModel (or data for that ViewModel, i.e. viewing two users in different tabs, but both use the same templated view).

User controls communicating via Commands - how?

I'm working on my first project in WPF/XAML, and there's a lot I've not figured out.
My problem is simple - I need a window that has a bunch of fields at the top, with which the user will enter his selection criteria, a retrieve button, and a data grid. When the user clicks on the button, a query is run, and the results are used to populate the grid.
Now the simple and obvious and wrong way to implement this is to have a single module containing a single window, and have everything contained within it - entry fields, data grid, the works. That kind of mangling of responsibilities makes for an unmaintainable mess.
So what I have is a window that is responsible for little more than layout, that contains two user controls - a criteria control that contains the entry fields and the retrieve button, and a data display control that contains the data grid.
The question is how to get the two talking to each other.
Years back, I would have added a function pointer to the criteria control. The window would have set it to point to a function in the display control, and when the button was clicked, it would have called into the display control, passing the selection criteria.
More recently, I would have added an event to the criteria control. I would have had the window set a handler in the display control to listen to the event, and when the button was clicked, it would have raised the event.
Both of these mechanisms would work, in WPF. But neither is very XAMLish. It looks to me like WPF has provided the ICommand interface specifically to accommodate these kinds of connection issues, but I've not yet really figured out how they are intended to work. And none of the examples I've seen seem to fit my simple scenario.
Can anyone give me some advice on how to fit ICommand to this problem? Or direct me to a decent explanation online?
Thanks!
MVVM is the prevalent pattern used with WPF and Silverlight development. You should have a read up on it.
Essentially, you would have a view model that exposes a command to perform the search. That same view model would also expose properties for each of your criteria fields. The view(s) would then bind to the various properties on the view model:
<TextBox Text="{Binding NameCriteria}"/>
...
<Button Command="{Binding SearchCommand}".../>
...
<DataGrid ItemsSource="{Binding Results}"/>
Where your view model would look something like:
public class MyViewModel : ViewModel
{
private readonly ICommand searchCommand;
private string nameCriteria;
public MyViewModel()
{
this.searchCommand = new DelegateCommand(this.OnSearch, this.CanSearch);
}
public ICommand SearchCommand
{
get { return this.searchCommand; }
}
public string NameCriteria
{
get { return this.nameCriteria; }
set
{
if (this.nameCriteria != value)
{
this.nameCriteria = value;
this.OnPropertyChanged(() => this.NameCriteria);
}
}
}
private void OnSearch()
{
// search logic, do in background with BackgroundWorker or TPL, then set Results property when done (omitted for brevity)
}
private bool CanSearch()
{
// whatever pre-conditions to searching you want here
return !string.IsEmpty(this.NameCriteria);
}
}

How to get an ItemsSource to refresh its bind?

I've got a view which shows a listbox that is bound to GetAll():
<DockPanel>
<ListBox ItemsSource="{Binding GetAll}"
ItemTemplate="{StaticResource allCustomersDataTemplate}"
Style="{StaticResource allCustomersListBox}">
</ListBox>
</DockPanel>
GetAll() is an ObservableCollection property in my ViewModel:
public ObservableCollection<Customer> GetAll
{
get
{
return Customer.GetAll();
}
}
which in turn calls a GetAll() model method which reads an XML file to fill the ObservableCollection.:
public static ObservableCollection<Customer> GetAll()
{
ObservableCollection<Customer> customers = new ObservableCollection<Customer>();
XDocument xmlDoc = XDocument.Load(Customer.GetXmlFilePathAndFileName());
var customerObjects = from customer in xmlDoc.Descendants("customer")
select new Customer
{
Id = (int)customer.Element("id"),
FirstName = customer.Element("firstName").Value,
LastName = customer.Element("lastName").Value,
Age = (int)customer.Element("age")
};
foreach (var customerObject in customerObjects)
{
Customer customer = new Customer();
customer.Id = customerObject.Id;
customer.FirstName = customerObject.FirstName;
customer.LastName = customerObject.LastName;
customer.Age = customerObject.Age;
customers.Add(customer);
}
return customers;
}
This all works fine EXCEPT when the user goes to another view, edits the XML file and comes back to this view where the old data is still showing.
How can I tell this view to "refresh its bindings" so that it shows the actual data.
It feels like I am going about WPF here with too much of an HTML/HTTP metaphor, I sense there is a more natural way to get ObservableCollection to update itself, hence its name, but this is the only way I can get the user to be able to edit data in a WPF application at the moment. So help on any level is appreciated here.
An ItemsControl requests its binding once and caches the reference thereafter.
If the content of the collection object are modified, and it implements INotifyCollectionChanged (as ObservableCollection does), it will pick up any added or removed object.
Now, if you want the binding to supply a new collection object to the ListBox, you can have your view-model implement INotifyPropertyChanged and raise PropertyChanged, passing in GetAll as the property name.
This will have the effect of warning the binding that the property value has changed (there is a new ObservableCollection ready to be picked up), which it will supply to the ListBox, which will re-generate its items.
So as long as you effect changes from your app, working on the ObservableCollection returned by GetAll, you can add and remove and the list will stay in synch. When you want to pick up external modifications (you might have a refresh button somewhere, or a natural point where it makes sense to reload the whole file), you can have your view-model raise the PropertyChanged event, which will automatically call the property getter, which will call the static method, which will return a fresh new collection.
Nitpicker note: why do you give method names to properties?
Below line work same as when we remove to add object in collection:
CollectionViewSource.GetDefaultView(CustomObservableCollection).Refresh();
Keep a reference to your ObservableCollection and the XML file's last-modified time as of the time you loaded it. Whenever the window gets focus, check the timestamp on the disk file. If it's changed, clear and re-populate the ObservableCollection. The GUI is automatically listening for change events from the ObservableCollection and will re-populate automatically when you modify the collection's contents.

Resources