I have a Xamarin Forms app where I use the Master-Detail template, in combination with MVVM. It suites my needs quite well. The thing is that I would occasionally show some sort of information in a notification-like style in the top bar of the displayed view. This would sometimes be an error message like "Server not reachable" or "Action not allowed".
The thing is that it seems the only way to put something into that bar is via the ContentPage.ToolbarItems, which does not have a DataBinding or any way to be accessed via the ViewModel.
A possible solution I came up with is passing the view as an argument to the ViewModel, and from there do something like view.ToolbarItems.Add(new ToolbarItem("abc", null, () => { }));. But this is breaking the MVVM on a quite basic level, as the ViewModel has a reference to the View. It would be possible to mask the view with an interface, but I don't like this solution at all.
So, can I use DataBinding to dynamically add and remove a button to the top bar in a Xamarin Master-Detail app?
As you have already mentioned, it is never a good idea to have a reference to the view in the viewmodel because it breaks the MVVM pattern. In cases like yours, when binding is not available, a sensible/acceptable approach is to subscribe to the viewmodel from the view in order to update it if the viewmodel changes.
Add something like this to your view:
ViewModel.PropertyChanged += OnViewModelPropertyChanged;
...
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(ViewModel.YourProperty):
// Apply changes to the view here, for example:
ToolbarItems.Add(new ToolbarItem(ViewModel.YourProperty, null, () => { }));
break;
}
}
I hope this helps!
Related
I have a Content View in Xamarin forms with 2 StackLayout horizontally aligned in it. I want to change the Content of the 2nd Stacklayout dynamically, but I don't want to use Master details page in that case. Please find an attachment to see what my UI looks like. I want to load different pages in StackLayout 2 on button clicks in StackLayout 1.
Update: I want to achieve above using MVVM.
You specifically said you want to load different pages inside Stacklayout2.
Out of the box, this is not possible. A Page can not be nested inside another view in Xamarin.Forms*
However, you most likely don't need to nest a whole page either, which is good news.
You can create custom xaml views as separate xaml files, and then reference them like regular controls. For example you would create a xaml file MyDataView, inside you could use a and fill it out with your different labels, entries and what nots, then instantiate and add that MyDataView inside your page like you would any other control.
For your host page, I would recommend you change your StackLayout2 to a ContentView, as it will only ever contain one view, which in case of something like your custom "MyDataView" from above, would actually contain the stack layout and all the details.
From the point of view of your page, it has the left layout with all the buttons, and it has the "container" on the right to host different complex views. So it does not need a stacklayout there.
There is also an important decision that you need to make on when and how you want to instantiate all the views that you will host inside the right pane.
You might choose to instantiate them all at once, when loading the page, if there aren't too many. Then switchig to each one during page use should be quite quick. Something like this:
public partial class MainPage
{
private MyDataView myDataView = new myDataView();
private OtherView otherView = new OtherView();
private ThirdView thirdView = new ThirdView();
public void OnSomeButtonClick(object sender, EventArgs e)
{
Container.View = myDataView; //Container would be the x:Name you gave to your ContentView control on the right side of the page that will host the different views
}
}
Or you might prefer to "lazy" instantiate the views, which would only instantiate the view the first time it is navigated to. This is useful if some views will never actually be accessed, and you can save some cpu cycles and ram by not loading the view until it's needed. The downside of course is when you do load it for the first time, it will load slower. Like this:
public partial class MainPage
{
private MyDataView myDataView;
private OtherView otherView;
private ThirdView thirdView;
public void OnSomeButtonClick(object sender, EventArgs e)
{
if (myDataView == null) myDataView = new MyDataView();
Container.View = myDataView; //Container would be the x:Name you gave to your ContentView control on the right side of the page that will host the different views
}
}
And finally you can instantiate the view everytime it is needed. I would not go with this route in most cases. Unless you really specifically need to recreate the entire view each time (like if you are dynamically changing it during use, and you need to reset it when it's shown again)
*I have seen a custom renderer implementation that nested a whole page inside a view on github. I did not test it as it was not completely implemented at the time.
Cant you do something like this:
button1.OnClick += (sender, args) =>{
StackLayout2.Children.Remove(currentview);
StackLayout2.Children.Add(newview);
}
Not sure if its called onclick or clicked.
$('#my-view').on('show', showHandler)
Doesn't work. Using data-show is not an option either because the code that sets/unsets the event is within class that is instantiated later. Also creating the view programatically and passing in the event handler doesn't work because I need to set the event on/off at different times.
Is this not possible with kendoUI? If not, why? This seems like such an incredibly obvious feature to relay those events to the element themselves similar to what is possible with jquery ui widgets.
This works:
var view = $('#my-view');
var widget = kendo.widgetInstance(view);
widget.bind('show', showHandler);
Better answer, just delegate the events yourself so the code in the question actually works:
<div data-role="view" ... data-show="onShow">...</div>
and
function onShow () {
this.element.trigger('show');
}
Now the it works :). The problem I still has was that 'show' isn't triggered when a view is first shown if it is the first view shown. Er, so yeah I had to add some extra code for that too like this:
if ($('#my-view').is(':visible')) {
$('#my-view').trigger('show');
}
Lame but it works.
Ok, so what I am looking to do is to display some sort of login control (maybe a UserControl with a TextBox and PasswordBox) when the app is started.
In a non-mvvm situation, a way of doing this would be to use the PopUp primitive control, add the usercontrol as a child element and off you go.
In an MVVM situation, i'm a bit confused about how you would achieve a simmilar result.
I have looked into messaging with the DialogMessage and this is fine for displaying a typical MessageBox, but what about a custom usercontrol?
any help would be fantastic! I can't seem to find any demo code of this anywhere.
In a MVVM situation you can use a delegate to let your View open the dialog when the ViewModel requests it.
You define a delegate at the VM:
public Func<LoginResult> ShowLoginDialogDelegate;
In your View you define the function that will be called:
private LoginResult ShowLoginDialog()
{
LoginResult result;
// show a dialog and get the login data
return result;
}
Then you "connect" the delegate and method in the View:
_viewModel = new MyViewModel();
DataContext = _viewModel;
_viewModel.ShowLoginDialogDelegate += ShowLoginDialog;
And now you can use it in your ViewModel e.g. when a command is executed like that:
LoginResult result = ShowLoginDialogDelegate();
An easier answer is to control it's visibility through a View State which with a little manipulation can be made to work through databinding allowing the view model to display the "Logon Page" state when required.
I just recently wrote about this for the Silverlight/XNA series which you can view here.
It would be much simplier if the SL4 DataEventrigger was available but hay ho.
I'm looking for the best practice on how to pass data from page to page.
In Page A I have a button that fires off Page B.
On Page B I have 6 textboxes that allow the user to enter information.
When the user is done, the click on a button that brings them back to Page A.
I want to pass that data back to Page A.
I've seen suggestions to:
build XML documents and save to Isolated Storage
use the App class to store information in properties
pass it like a query string
I'm looking for the Best practice. Is there one that Microsoft recommends or one that is generally accepted as the best way?
Thanks
PhoneApplicationService.Current.State["yourparam"] = param
NavigationService.Navigate(new Uri("/view/Page.xaml", UriKind.Relative));
then in other page simply
var k = PhoneApplicationService.Current.State["yourparam"];
Personally I'd store the values entered on Page B in a model(object) that is also accessible to Page A.
Depending on how you're navigating to Page A the second time, one or more of the following may be usful to help understand passing values between pages:
How to pass the image value in one xaml page to another xaml page in windows phone 7?
Passing a complex object to a page while navigating in a WP7 Silverlight application
How to pass an object from a xaml page to another?
How to pass a value between Silverlight pages for WP7?
How do I navigate from one xaml page to another, and pass values?
One thing you can consider is to use MVC: let your App be the controller, store all data in the model, and the pages are just views that contains pure UI logic. In this case your pages are painters and you pass your model object around. This gives nice isolation of business logic and the UI so that you can rev them easily.
BTW, Silverlight and XAML are great tools for MVC so it's a natural match.
There's a couple of things at play here. First of all, if/when the user uses the Back button to return to page A instead of your button, is the information in the text boxes exchanged or not (is Back = Cancel, or is Back = OK?)
That said, if you're using NavigationService.GoBack (which you should be instead of NavigationService.Navigate, because if you use the Navigate call, repeated hits of the back key will cause all kinds of bad UX for your users), then QueryStrings are not an option. Because pages really have no way to reference each other in the WP7 Silverlight nav system, you need to use a 3rd party to hold your data. For that, you can turn to (a) Isolated Storage (slow & heavy, but fail-safe), (b) Use the PhoneApplicationService.State dictionary, or (c) use Global properties of some kind, either hung off of the application object, or using Statics/Singletons...
Remember to watch for Tombstoning behavior when you do this - your page will process the OnNavigatedTo method when (a) you navigate into it in your application (b) you navigate back to it when you complete your work on Page B, or (c) you tombstone your app from that page and return to your application using the Back key.
Sorry I didn't give a more direct answer there - a lot depends on your specific circumstances. In the most general case, I'd strongly consider using the App State Dictionary on the PhoneApplicationService...it is lightweight, easy to use, and survives tombstoning. Just be sure that your keys are as unique as they need to be.
If you create a new Windows Phone project and use the Windows Phone Databound Template you will have most of the work done for you.
What you will want to do is set up the ViewModel to contain all the data for your app. You can serialize and deserialize this data using IsolatedStorage so that it's saved across application sessions and when Tombstoning.
In the template you will notice MailViewModel and ItemViewModel. MainViewModel stores all the data your application needs including an ObservableCollection of ItemViewModel, and ItemViewModel represents the individual data type for your application.
On the DetailsPage.xaml page you'll want to DataBind each textbox to the App.MainViewModel Items. Set the binding to TwoWay if you want the ViewModel to get updated as soon as the user manipulates the data on DetailsPage.xaml. You can optionally set the Binding to OneWay and then have an OK button that writes the changes back to the ViewModel and saves to IsolatedStorage.
Here is an example of what a Binding looks like:
<TextBlock x:Name="ListTitle" Text="{Binding LineOne}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
In this case LineOne is a property in ItemViewModel and the page gets this data from the query string when the user selects an item from the MainPage.xaml. The DataContext for the page determs where the databound information comes from.
Here is the snippet where the MainPage passes the selected item from the ViewModel to the DetailsPage.
// Handle selection changed on ListBox
private void MainListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// If selected index is -1 (no selection) do nothing
if (MainListBox.SelectedIndex == -1)
return;
// Navigate to the new page
NavigationService.Navigate(new Uri("/DetailsPage.xaml?selectedItem=" + MainListBox.SelectedIndex, UriKind.Relative));
// Reset selected index to -1 (no selection)
MainListBox.SelectedIndex = -1;
}
Here is how the DetailsPage gets the selected item.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
string selectedIndex = "";
if (NavigationContext.QueryString.TryGetValue("selectedItem", out selectedIndex))
{
int index = int.Parse(selectedIndex);
DataContext = App.ViewModel.Items[index];
}
}
Play around with the default template above and ask any additional questions.
The beauty of databinding and the ObservableCollection is that you can just update the data and the UX will reflect those changes immediatley. This is because any changes to the data fires off an event:
public string LineOne
{
get
{
return _lineOne;
}
set
{
if (value != _lineOne)
{
_lineOne = value;
NotifyPropertyChanged("LineOne");
}
}
}
NotifyPropertyChanged() that broadcasts this information to the View.
You can also keep it simple and use PhoneApplicationService.Current.State which is basically a hashtable. You will need to implement your own marshalling to and from isolated storage if you want anything to outlive the app.
Omar's suggestion to use the Windows Phone Databound Template is probably the best idea on this page. It amounts to the same as my suggestion but you will get a better result (more maintainable code) at the cost of a longer steeper learning curve.
I suggest you do it my way and then do it again Omar's way.
as i implemented like this.. Whether its correct or not i dont know..
When u click news list page it should open the news detail page.
I want to pass the selected news item contents from news List-Page to news-details Page.
the News list page contains following method.
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
NewsDetailsPage newsDetailPage = (e.Content as NewsDetailsPage);
if (newsDetailPage != null)
newsDetailPage.SelectedNewsItem = SelectedNewsItem; //Contains the news details
base.OnNavigatedFrom(e);
}
In the News details Page. U can access that(SelectedNewsItem) object.
This may or may not be correct.
One option is to use Application.Resources:
Store data:
Application.Current.Resources.Add("NavigationParam", customers);
NavigationService.Navigate(new Uri("/Page2.xaml", UriKind.Relative));
Retrieve data:
var customers = (List<Customer>) Application.Current.Resources["NavigationParam"];
Here's a blog post with describes this in more detail: http://mikaelkoskinen.net/windows-phone-pass-data-between-pages-application-resources/ (author: me)
In my winforms app, I have a UserControl that contains a DataGridView. I instantiate and load this UserControl when needed into a panel in my Main Form (frmMain). My problem is figuring out how to resond to or listen for events raised in my UC's DataGridView. For example, I want to handle the CellDoubleClick event of the DataGridView in my Main Form rather than through the UC.
Is this possible? I had thought of updating a property when the cell in the grid is double-clicked, and then let my Main form do whatever when that property changes - therefore I thought of using INotifyPropertyChanged. Im not heavily clued up on how to use it in m scenario however, and would deeply appreciate some help in this regard, or if anyone can suggest an alternate solution.
Much thanx!
Your user control must encapsulate some logic, so if you want to handle event of the DataGridView that is in your control the way you've described, you probably missing something in idea of user controls and encapsulation. Technically here two ways to do this:
Make a public property in your user control of type DataGridView.
Make an event wrapper. You will need to create an event in your user control that is raised when DataGridView CellDoubleClick (or any) is rased and in your calling code you will handle this event wrapper.
The second approach is more logical, cos internal logic of your control is incapsulated and you can provide end-user of you component with more logical and meaningful event then CellDoubleClidk or else.
thank u 4 your reply. Sorry for not responding earlier. I did manage to sort this issue out by creating a public event in my UC:
public event DataGridViewCellEventHandler GridRowDoubleClick {
add { dgvTasks.CellDoubleClick += value; }
remove { dgvTasks.CellDoubleClick -= value; }
}
and in my main form, after I instantiate and load the UC
_ucTask.GridRowDoubleClick += new DataGridViewCellEventHandler(TasksGrid_CellDoubleClick);
with the following attached event:
private void TasksGrid_CellDoubleClick( object sender, DataGridViewCellEventArgs e ) {
// do work here!
}
This does work, although I don't know if any of u experts out there foresee a problem with this approach.