learning wpf with mvvm (using EF as ORM).
In my view model i have the property:
//---------------ClientNew
public const string ClientNewConst = "ClientNew";
private TBL_CLIENT _clientNew = new TBL_CLIENT();
public TBL_CLIENT ClientNew
{
get
{
return _clientNew;
}
set
{
if (_clientNew == value)
{
return;
}
var oldValue = _clientNew;
_clientNew = value;
// Update bindings, no broadcast
RaisePropertyChanged(ClientNewConst);
}
}
where TBL_CLIENT - is an entittyobject that mirrors TBL_CLIENT table in DB
now, in my VIEW i bind bunch of textboxes like this(example only for client's first name):
<TextBox Style="{StaticResource ResourceKey=entryFormTextBox}"
Text="{Binding ClientNew.CLIENT_FIRST_NAME,
ValidatesOnDataErrors=True,
NotifyOnValidationError=true,
ValidatesOnExceptions=True,
UpdateSourceTrigger=LostFocus}"
Grid.Column="1"
Grid.Row="1" />
I tried to use different triggers for updatesource.. still teh validation does not work.
oh, i do have implemented idataerrorinfo interface in my viewmodel (but it never hits it..)
#region IDataErrorInfo Members
string IDataErrorInfo.Error
{
get { throw new NotImplementedException(); }
}
string IDataErrorInfo.this[string columnName]
{
get
{
if (string.IsNullOrEmpty("ClientNew.CLIENT_FIRST_NAME"))
{
return "Client Name is required";
}
return null;
}
}
#endregion
so, question.. how can i implement the simple as possible validation using idataerrorinfo for my case, where i don't have the separate property defined in ModelView for each of the entity object, but the property takes the whole entity object?
thanks in advance,
Alex
You might have a look at the BookLibrary sample application of the WPF Application Framework (WAF). It defines the validation rules directly at the entity. Please have a look at "BookLibrary.Domain / Book.cs".
Related
I have a variable in App.cs:
public static string StatusText = "";
And ViewModel like this:
public string StatusText
{
get { return App.StatusText; }
set
{
if (value == App.StatusText) return;
App.StatusText = value;
OnPropertyChanged();
}
}
In the view I have a label like this:
<Label x:Name="TxtReport" Text="{Binding StatusText}" />
My intent is to have some sort of handle from the View to change StatusText programmatically so any Label bound to it will automatically update.
Also I don't want to assign TxtReport.Text directly because I can only do this safely from the main thread (or use BeginInvokeOnMainThread).
What's the best approach? I have seen suggestions of using MessagingCenter. Is this the only way?
I have Button in ListView Cell. On button click I need to perform two actions from ViewModel
Get the current record (to make modification in data)
Get the current Cell/View (to modify text, bg color of button)
However, I am able to perform single action at a time using DelegateCommand by passing Student and object param respectively. See my code below
public StudentAttendanceListPageViewModel()
{
//BtnTextCommand=new DelegateCommand<object>(SetBtnText);
ItemCommand=new DelegateCommand<Student>(BtnClicked);
}
public DelegateCommand<object> BtnTextCommand { get; private set; }
public void SetBtnText(object sender)
{
if (view.Text == "P")
{
view.Text = "A";
view.BackgroundColor= (Color)Application.Current.Resources["lighRedAbsent"];
}
}
public DelegateCommand<Student> ItemCommand { get; }
public void BtnClicked(Student objStudent)
{
objStudent.AbsentReasonId="1001";
objStudent.AttendanceTypeStatusCD = "Absent";
objStudent.AttendanceTypeStatusId = "78001"
}
This is Button code
<Button x:Name="mybtn"
Command="{Binding Source={x:Reference ThePage}, Path=BindingContext.ItemCommand}"
CommandParameter="{Binding .}"
BackgroundColor="{DynamicResource CaribGreenPresent}"
Text="{Binding AttendanceTypeStatusId, Converter={x:StaticResource IDToStringConverter}}">
</Button>
If you see above code I have two methods SetBtnText and BtnClicked. How can I merge these two methods into one by passing Student and object params at a time in DelegateCommand?
You should bind the view's properties to the view model. Then pass the view model as command parameter and change whatever you want to change in the command and data binding will automatically update the view.
Example:
<Button Command="{Binding SomeCommand}"
Text="{Binding Text}">
</Button>
public class StudentViewModel
{
public StudentViewModel( Student student )
{
_text = $"Kick {student.Name}";
SomeCommand = new DelegateCommand( () => {
Text = "I have been kicked"
student.Exmatriculate();
SomeCommand.RaiseCanExecuteChanged();
},
() => student.IsMatriculated
);
}
public DelegateCommand SomeCommand { get; }
public string Text
{
get => _text;
set => SetProperty( ref _text, value );
}
private string _text;
}
As stated in the comments already, it's never ever necessary to pass the view to the view model. To me, it looks as if you don't have a view model in the first place, though, as your code only mentions Student (which most likely is part of the model), while there's no occurence of a StudentViewModel. You know, you do not bind to the model directly unless it's a trivial toy project.
I have the following code in my Android app, it basically uses one page (using a NavigationDrawer) and swaps fragments in/out of the central view. This allows the navigation to occur on one page instead of many pages:
Setup.cs:
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
var customPresenter = new MvxFragmentsPresenter();
Mvx.RegisterSingleton<IMvxFragmentsPresenter>(customPresenter);
return customPresenter;
}
ShellPage.cs
public class ShellPage : MvxCachingFragmentCompatActivity<ShellPageViewModel>, IMvxFragmentHost
{
.
.
.
public bool Show(MvxViewModelRequest request, Bundle bundle)
{
if (request.ViewModelType == typeof(MenuContentViewModel))
{
ShowFragment(request.ViewModelType.Name, Resource.Id.navigation_frame, bundle);
return true;
}
else
{
ShowFragment(request.ViewModelType.Name, Resource.Id.content_frame, bundle, true);
return true;
}
}
public bool Close(IMvxViewModel viewModel)
{
CloseFragment(viewModel.GetType().Name, Resource.Id.content_frame);
return true;
}
.
.
.
}
How can I achieve the same behavior in a Windows UWP app? Or rather, is there ANY example that exists for a Windows MvvmCross app which implements a CustomPresenter? That may at least give me a start as to how to implement it.
Thanks!
UPDATE:
I'm finally starting to figure out how to go about this with a customer presenter:
public class CustomPresenter : IMvxWindowsViewPresenter
{
IMvxWindowsFrame _rootFrame;
public CustomPresenter(IMvxWindowsFrame rootFrame)
{
_rootFrame = rootFrame;
}
public void AddPresentationHintHandler<THint>(Func<THint, bool> action) where THint : MvxPresentationHint
{
throw new NotImplementedException();
}
public void ChangePresentation(MvxPresentationHint hint)
{
throw new NotImplementedException();
}
public void Show(MvxViewModelRequest request)
{
if (request.ViewModelType == typeof(ShellPageViewModel))
{
//_rootFrame?.Navigate(typeof(ShellPage), null); // throws an exception
((Frame)_rootFrame.UnderlyingControl).Content = new ShellPage();
}
}
}
When I try to do a navigation to the ShellPage, it fails. So when I set the Content to the ShellPage it works, but the ShellPage's ViewModel is not initialized automatically when I do it that way. I'm guessing ViewModels are initialized in MvvmCross using OnNavigatedTo ???
I ran into the same issue, and built a custom presenter for UWP. It loans a couple of ideas from an Android sample I found somewhere, which uses fragments. The idea is as follows.
I have a container view which can contain multiple sub-views with their own ViewModels. So I want to be able to present multiple views within the container.
Note: I'm using MvvmCross 4.0.0-beta3
Presenter
using System;
using Cirrious.CrossCore;
using Cirrious.CrossCore.Exceptions;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Views;
using Cirrious.MvvmCross.WindowsUWP.Views;
using xxxxx.WinUniversal.Extensions;
namespace xxxxx.WinUniversal.Presenters
{
public class MvxWindowsMultiRegionViewPresenter
: MvxWindowsViewPresenter
{
private readonly IMvxWindowsFrame _rootFrame;
public MvxWindowsMultiRegionViewPresenter(IMvxWindowsFrame rootFrame)
: base(rootFrame)
{
_rootFrame = rootFrame;
}
public override async void Show(MvxViewModelRequest request)
{
var host = _rootFrame.Content as IMvxMultiRegionHost;
var view = CreateView(request);
if (host != null && view.HasRegionAttribute())
{
host.Show(view as MvxWindowsPage);
}
else
{
base.Show(request);
}
}
private static IMvxWindowsView CreateView(MvxViewModelRequest request)
{
var viewFinder = Mvx.Resolve<IMvxViewsContainer>();
var viewType = viewFinder.GetViewType(request.ViewModelType);
if (viewType == null)
throw new MvxException("View Type not found for " + request.ViewModelType);
// Create instance of view
var viewObject = Activator.CreateInstance(viewType);
if (viewObject == null)
throw new MvxException("View not loaded for " + viewType);
var view = viewObject as IMvxWindowsView;
if (view == null)
throw new MvxException("Loaded View is not a IMvxWindowsView " + viewType);
view.ViewModel = LoadViewModel(request);
return view;
}
private static IMvxViewModel LoadViewModel(MvxViewModelRequest request)
{
// Load the viewModel
var viewModelLoader = Mvx.Resolve<IMvxViewModelLoader>();
return viewModelLoader.LoadViewModel(request, null);
}
}
}
IMvxMultiRegionHost
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.WindowsUWP.Views;
namespace xxxxx.WinUniversal.Presenters
{
public interface IMvxMultiRegionHost
{
void Show(MvxWindowsPage view);
void CloseViewModel(IMvxViewModel viewModel);
void CloseAll();
}
}
RegionAttribute
using System;
namespace xxxxx.WinUniversal.Presenters
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class RegionAttribute
: Attribute
{
public RegionAttribute(string regionName)
{
Name = regionName;
}
public string Name { get; private set; }
}
}
These are the three foundational classes you need. Next you'll need to implement the IMvxMultiRegionHost in a MvxWindowsPage derived class.
This is the one I'm using:
HomeView.xaml.cs
using System;
using System.Diagnostics;
using System.Linq;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.WindowsUWP.Views;
using xxxxx.Shared.Controls;
using xxxxx.WinUniversal.Extensions;
using xxxxx.WinUniversal.Presenters;
using xxxxx.Core.ViewModels;
namespace xxxxx.WinUniversal.Views
{
public partial class HomeView
: MvxWindowsPage
, IMvxMultiRegionHost
{
public HomeView()
{
InitializeComponent();
}
// ...
public void Show(MvxWindowsPage view)
{
if (!view.HasRegionAttribute())
throw new InvalidOperationException(
"View was expected to have a RegionAttribute, but none was specified.");
var regionName = view.GetRegionName();
RootSplitView.Content = view;
}
public void CloseViewModel(IMvxViewModel viewModel)
{
throw new NotImplementedException();
}
public void CloseAll()
{
throw new NotImplementedException();
}
}
}
The last piece to make this work is the way the actual xaml in the view is set-up. You'll notice that I'm using a SplitView control, and that I'm replacing the Content property with the new View that's coming in in the ShowView method on the HomeView class.
HomeView.xaml
<SplitView x:Name="RootSplitView"
DisplayMode="CompactInline"
IsPaneOpen="false"
CompactPaneLength="48"
OpenPaneLength="200">
<SplitView.Pane>
// Some ListView with menu items.
</SplitView.Pane>
<SplitView.Content>
// Initial content..
</SplitView.Content>
</SplitView>
EDIT:
Extension Methods
I forgot to post the two extension methods to determine if the view declares a [Region] attribute.
public static class RegionAttributeExtentionMethods
{
public static bool HasRegionAttribute(this IMvxWindowsView view)
{
var attributes = view
.GetType()
.GetCustomAttributes(typeof(RegionAttribute), true);
return attributes.Any();
}
public static string GetRegionName(this IMvxWindowsView view)
{
var attributes = view
.GetType()
.GetCustomAttributes(typeof(RegionAttribute), true);
if (!attributes.Any())
throw new InvalidOperationException("The IMvxView has no region attribute.");
return ((RegionAttribute)attributes.First()).Name;
}
}
Hope this helps.
As the link to the blog of #Stephanvs is no longer active I was able to pull the content off the Web Archive, i'll post it here for who ever is looking for it:
Implementing a Multi Region Presenter for Windows 10 UWP and MvvmCross
18 October 2015 on MvvmCross, Xamarin, UWP, Windows 10, Presenter > Universal Windows Platform
I'm upgrading a Windows Store app to the new Windows 10 Universal
Windows Platform. MvvmCross has added support for UWP in v4.0-beta2.
A new control in the UWP is the SplitView control. Basically it
functions as a container view which consist of two sub views, shown
side-by-side. Mostly it's used to implement the (in)famous hamburger
menu.
By default MvvmCross doesn't know how to deal with the SplitView, and
just replaces the entire screen contents with a new View when
navigating between ViewModels. If however we want to lay-out our views
differently and show multiple views within one window, we need a
different solution. Luckily we can plug-in a custom presenter, which
will take care of handling the lay-out per platform.
Registering the MultiRegionPresenter
In the Setup.cs file in your UWP project, you can override the
CreateViewPresenter method with the following implementation.
protected override IMvxWindowsViewPresenter CreateViewPresenter(IMvxWindowsFrame rootFrame)
{
return new MvxWindowsMultiRegionViewPresenter(rootFrame);
}
Using Regions
We can define a region by declaring a
element. At this point it has to be a Frame type because then we can
also show a nice transition animation when switching views.
<mvx:MvxWindowsPage ...>
<Grid>
<!-- ... -->
<SplitView>
<SplitView.Pane>
<!-- Menu Content as ListView or something similar -->
</SplitView.Pane>
<SplitView.Content>
<Frame x:Name="MainContent" />
</SplitView.Content>
</SplitView>
</Grid>
</mvx:MvxWindowsPage>
Now we want to be able when a ShowViewModel(...) occurs to swap out
the current view presented in the MainContent frame.
Showing Views in a Region
In the code-behind for a View we can now declare a MvxRegionAttribute,
defining in which region we want this View to be rendered. This name
has to match a Frame element in the view.
[MvxRegion("MainContent")]
public partial class PersonView
{
// ...
}
It's also possible to declare multiple regions within the same view.
This would allow you to split up your UI in more re-usable pieces.
Animating the Transition between Content Views
If you want a nice animation when transitioning between views in the
Frame, you can add the following snippet to the Frame declaration.
<Frame x:Name="MainContent">
<Frame.ContentTransitions>
<TransitionCollection>
<NavigationThemeTransition>
<NavigationThemeTransition.DefaultNavigationTransitionInfo>
<EntranceNavigationTransitionInfo />
</NavigationThemeTransition.DefaultNavigationTransitionInfo>
</NavigationThemeTransition>
</TransitionCollection>
</Frame.ContentTransitions>
</Frame>
The contents will now be nicely animated when navigating.
Hope this helps, Stephanvs
I'm trying to add a name to a label so I can find it using the Content.FindByName<>() method
as far as I can tell the Xamarin.Forms.Label() class doesn't have a Name property
Here is my page class
public class HelloWordCodePage : ContentPage
{
public Label HelloWorldLabel;
public HelloWordCodePage()
{
HelloWorldLabel = new Label{Text = "Hello World", };
ConstructView();
}
public void ConstructView()
{
Content = new StackLayout
{
Children =
{
HelloWorldLabel
}
};
}
}
If you define your label in Xaml, you can use this syntex:
<Label x:Name="YourLableName" Text="A simple Label" />
And then access it in the code behind like this
YourLableName.Text = "bla bla"
However, if you don't use Xaml, and all your page definition is found in the code behind, you should keep a reference to that label in order to access it instead of finding it using Content.FindByName<>() as #Sten Petrov commented
I believe the name is only meant to be used when creating the element in XAML. If you look at the inheritance you will find Element at the bottom and it implements INameScope explicitly. You cannot cast Label to INameScope as that is an internal interface:
namespace Xamarin.Forms
{
internal interface INameScope
{
object FindByName(string name);
void RegisterName(string name, object scopedElement);
void UnregisterName(string name);
}
}
The Name is assigned at the Render processing stage. There is no need to know the rendered name at this time as you are working on a cross-platform ContentPage.
There is a GUID, but like you say there is no ID. You wouldn't in my opinion ever want to get a platform-specific ID for the generated control at this high a level of page design. If you do then perhaps you need to create a platform-specific PageRenderer instead?
When you start to write CustomRenderers and create platform specific controls you can then assign IDs if you so wish any custom control platform specific implementations you want.
Remember on the current ContentPage - you have a class-scoped object reference already to your label named HelloWorldLabel, that you can use to change its appearance etc on the page when in execution.
StyleId
Use the StyleId property to identify individual elements in your application
for identification in ui testing and in theme engines.
As IdoT says, if you define an object at runtime, you need to keep a reference to that in order to access it as it is not possible to assign a name.
I use a dictionary like this:
Dictionary<Guid, String> ControlsDictionary = new Dictionary<Guid, string>();
Then when I create an object:
Button bt = new Button {Text="Yay!"};
bt.Clicked += OnButtonClicked;
StackLayout.Children.Add(bt);
ControlsDictionary.Add(bt.Id, "myname");
In the Click event handler I can identify the sender "name":
async void OnButtonClicked(object sender, EventArgs args)
{
Button btn = (Button)sender;
String NameofButton = ControlsDictionary[btn.Id];
}
also you can obtain the Id from the "name":
Guid IdfromName = ControlsDictionary.First(x => x.Value == "myname").Key;
It is bit late but was working through some of xamarin forms issues and remebered I did some thing similar in past, before I started implimenting MVVM. This is how I used Content.FindByName<>() is a rough exmple below.
XAML
<BoxView Color="Gray" HeightRequest="2" HorizontalOptions="Fill" />
<Label x:Name="DoorMaterial" x:Uid="41" Text="Door Material"/>
<RadioButton GroupName="Door_Material" x:Name="RB_99" Value="99" ClassId="41" CheckedChanged="RB_CheckedChanged" Content="Wood" ></RadioButton>
<RadioButton GroupName="Material" x:Name="RB_100" Value="100" ClassId="41" CheckedChanged="RB_CheckedChanged" Content="Steel" ></RadioButton>
<RadioButton GroupName="Material" x:Name="RB_101" Value="101" ClassId="41" CheckedChanged="RB_CheckedChanged" Content="Upvc" ></RadioButton>
<RadioButton GroupName="Material" x:Name="RB_102" Value="102" ClassId="41" CheckedChanged="RB_CheckedChanged" Content="Composite" ></RadioButton>
C#
private void SetPageFields(ICollection<DoorTemplateAnswerRecordModel> answerRecordModelsList)
{
string RadioButtonName = "RB_";
foreach (var answerRecordModel in answerRecordModelsList)
{
var answerId = answerRecordModel.AnswerId.ToString();
var combinedWihtId = RadioButtonName + answerId;
var getCheckBox = Content.FindByName<RadioButton>(combinedWihtId);
if (getCheckBox != null)
{
getCheckBox.IsChecked = true;
}
}
}
I'm just switching a project across to mvvmlight and trying to do things "the right way"
I've got a simple app with a listbox
When an item is selected in the listbox, then I've hooked up a RelayCommand
This RelayCommand causes a call on an INavigationService (http://geekswithblogs.net/lbugnion/archive/2011/01/06/navigation-in-a-wp7-application-with-mvvm-light.aspx) which navigates to a url like "/DetailPage.xaml?DetailId=12"
The DetailPage.xaml is then loaded and ... this is where I'm a bit unsure...
how should the DetailPage get hooked up to a DetailView with DetailId of 12?
should I do this in Xaml somehow using a property on the ViewLocator?
should I do this in the NavigatedTo method?
Please feel free to point me to a full sample - sure this has been done a (hundred) thousand times before, but all the blogs and tutorials seem to be skipping this last trivial detail (focussing instead on the messaging and on the ioc on on the navigationservice)
Thanks!
The only place you can retrieve the URL parameter is in the view. So since your view is likely depending on it, you should fetch it in the OnNavigatedTo method.
Then, you should pass it along to your viewmodel, either using messaging (to expensive if you ask me), or by referring to your datacontext (which is the viewmodel I presume), and execeuting a method on that.
private AddTilePageViewModel ViewModel
{
get
{
return DataContext as AddTilePageViewModel;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var postalCode = NavigationContext.TryGetKey("PostalCode");
var country = NavigationContext.TryGetStringKey("Country");
if (postalCode.HasValue && string.IsNullOrEmpty(country) == false)
{
ViewModel.LoadCity(postalCode.Value, country);
}
base.OnNavigatedTo(e);
}
I'm using some special extensions for the NavigationContext to make it easier.
namespace System.Windows.Navigation
{
public static class NavigationExtensions
{
public static int? TryGetKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
string value = source.QueryString[key];
int result = 0;
if (int.TryParse(value, out result))
{
return result;
}
}
return null;
}
public static string TryGetStringKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
return source.QueryString[key];
}
return null;
}
}
}
Create a new WindowsPhoneDataBound application, it has an example of how to handle navigation between views. Basically you handle the navigation part in your view, then set the view's DataContext accord to the query string. I think it plays nicely with the MVVM pattern since your ViewModels don't have to know anything about navigation (which IMO should be handled at the UI level).