I was trying to figure out how to do the data validation under UWP, but according to what I have found out, there is basically nothing I can implemented yet.
Due to that I tried to implement my custom validation logic. Problem I have now is, that I am showing error information on one TextBlock rather than directly under the specific TextBox which contains data error.
This is what I do at the moment:
public class Customer : ViewModel
{
private string _Name = default(string);
public string Name { get { return _Name; } set { SetProperty(ref _Name, value); OnPropertyChanged("IsValid"); } }
private string _Surname = default(string);
public string Surname { get { return _Surname; } set { SetProperty(ref _Surname, value); OnPropertyChanged("IsValid"); } }
private DateTime _DateOfBirth = default(DateTime);
public DateTime DateOfBirth { get { return _DateOfBirth; } set { SetProperty(ref _DateOfBirth, value); OnPropertyChanged("IsValid"); } }
public int ID { get; set; }
public bool IsValid
{
get
{
//restart error info
_ErrorInfo = default(string);
if (string.IsNullOrWhiteSpace(Name))
_ErrorInfo += "Name cannot be empty!" + Environment.NewLine;
if (string.IsNullOrWhiteSpace(Surname))
_ErrorInfo += "Surname cannot be empty!" + Environment.NewLine;
//raise property changed
OnPropertyChanged("ErrorInfo");
return !string.IsNullOrWhiteSpace(Name) &&
!string.IsNullOrWhiteSpace(Surname);
}
}
private string _ErrorInfo = default(string);
public string ErrorInfo { get { return _ErrorInfo; } set { SetProperty(ref _ErrorInfo, value); } }
}
Question:
How to adjust my code, so that rather than having one label with all error information, I can assign label under each textbox and display validation error there? Should I use Dictionary for this? If yes, how can I bind it to my View?
I have quickly become a fan of using Prism, see this wonderful demonstration User input validation with Prism and data annotations on the UWP.
Its better than anything I could type here.
You can make a flyout inside a textbox.
As soon as the textbox loses focus with wrong input, the flyout shows up .
You can set the placament of the flyout on top/bottom/side of the textbox.
Best of luck !
The problem with Prism is that it uses a string indexer. But Bind in uwp just will not allow string indexes... Integers only! There are also some key features lacking such as coordination between entity view models and between them and the context.
I've done some R&D and it seems that the following are key elements of a good validator in uwp
- use of strings as the binding target, to avoid dropping conversion exceptions
- tracking conversion errors separately from validation errors
- base class for the validating view model AND automatically generated derived classes specifying the property names
- events to tie multiple view models together so that multiple parts of the ui are kept consistent
- centralized error count and save / revert ability associated with the context
Anything out there that can do that? If so then I haven't found it yet.
sjb
Related
I have 3 textboxes and I am trying to add validation hints when it doesn't meet specific conditions. The conditions are as follows:
Disable submit button and show hints until textbox1 <= textbox2 and textbox3 >= 15% of textbox2
Right now I can only figure out how to check a length or make it required with this:
public class AttributeValidationViewModel : AnnotationValidationViewModel
{
private string _FirstName;
[Required(ErrorMessage = "# of containers is required")]
[MinLength(3, ErrorMessage = "textbox must have at least three characters")]
public string FirstName
{
get => _FirstName;
set => Set(ref _FirstName, value);
}
private string _LastName;
[Required]
public string LastName
{
get => _LastName;
set => Set(ref _LastName, value);
}
public RelayCommand SubmitCommand { get; }
public AttributeValidationViewModel()
{
SubmitCommand = new RelayCommand(OnSubmit, CanSubmit);
//Doing this will cause the errors to show immediately
ValidateModel();
}
private bool CanSubmit()
{
return !HasErrors;
}
private void OnSubmit()
{
Debug.WriteLine("Form Submittedffff");
}
I would like to add something like
[CustomCondition(ErrorMessage = "textbox must have at least three characters")] (like line 5 of the code)
I just can't figure out how I would do it. I know how to disable the button until conditions are meant but I am trying give validation hints on the textboxes aswell.
Data annotations aren't the way to perform this kind of validation that involves several properties at once.
What you should do is to implement the INotifyDataErrorInfo interface that was introduced in the .NET Framework 4.5. Please refer to this TechNet article for more information and example of how to implement it.
For my school assignment I have to make a reservationsystem for a hotel.
The thing is that I have to make the code without the UI (never done this before).
I have to add the UI later. Each UI should be able to be used with my code.
Now I have a class called Secretary
The Secretary is able to make a Reservation.
I have this method in the class Secretary :
public void CheckIn()
{
Reservation reservation = new Reservation();
reservation.ReservationDate1 = //info from a textbox
}
Now I know that I should connect everything when my UI is ready, but what is the best way to tell my code that he should get the information from the textbox when the textbox isn't there yet???
i would suggest you start by reading this
now as for what you have to do then
first you will need to model your data
public class Reservation
{
public DateTime Date{get;set;}
public string Name{get;set;}
public void Save(){/*Copy entry to DB, webservice, file, etc*/}
public void Delete(){/*delete entry from DB, webservice, file, etc*/}
//ect
}
as you can see you now have a list of what is required for a reservation, and functionality that will persist your data
next you need a ViewModel
public class ReservationViewModel:INotifyPropertyCHanged
{
public Reservation Reservation{get;set;} //Link to model
private DateTime _Date;
public DateTime Date
{
get { return _Date; }
set { SetProperty(ref _Date, value); }
}
private string _Name;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
public void SetProperty<T>(ref T store, T value,[CallerMemberName] string name = null)
{
store = value;
if(PropertyChanged!=null)PropertyChanged(this,new PropertyChangedArgs(name);
}
public void Save(){/*validate, copy over model values call models save*/}
public void Cancel(){/*change VM values back to Model values*/}
public void Delete(){/*validate, call models delete*/}
//ect
}
at this point you can stop as you have defined the data and behaviour of the system, though i would suggest adding a testing project to run your code and check it works
when you get to your View
you would just bind to your ViewModel and the rest is done for you
<TextBox Text={Binding Name}/>
You can use MVVM with a ViewModel, but if you just want a method ready to accept input when you design the UI, you can make Checkin() take a string parameter so it's Checkin(string value) and assign value to ReservationDate1.
public void CheckIn(string val)
{
Reservation reservation = new Reservation();
reservation.ReservationDate1 = val;
}
This is an exercise in keeping your logic and your UI nice and separate. A little more tightly coupled, but doable, would be this:
public void CheckIn(TextBox tb)
{
Reservation reservation = new Reservation();
reservation.ReservationDate1 = tb.Text;
}
I'm using SQLite in my application to store some Message objects. I display a list of Message items in "View 1". When I change a property in the Edit View "View 2", I want the property to also change in the list.
Classes
Message.cs
class Message : INotifyPropertyChanged
{
private uint _id;
public uint Id
{
get
{
return _id;
}
set
{
// Trigger INotifyPropertyChanged
Set("Id", ref _id, value);
}
}
private string _content;
public string Content
{
get
{
return _content;
}
set
{
// Trigger INotifyPropertyChanged
Set("Content", ref _content, value);
}
}
...
}
MessageViewModel.cs
class MessageViewModel : INotifyPropertyChanged
{
private Message message;
...
private string _content;
public string Content
{
get
{
return message.Content;
}
set
{
// Set value
message.Content = value;
// Trigger INotifyPropertyChanged
RaisePropertyChanged();
}
}
...
}
View 1
View1.xaml
The datacontext is View1ViewModel
View1ViewModel.cs
private List<MessageViewModel> _messages;
public List<MessageViewModel> Messages
{
get
{
return _messages;
}
set
{
// Trigger INotifyPropertyChanged
Set("Messages", ref _messages, value);
}
}
...
private async void loadMessages()
{
// Get the messages from SQLite database
var messages = await newMessages();
Messages = new MessageViewModelCollection(messages);
}
View 2
View2ViewModel.cs
private MessageViewModel _message;
public MessageViewModel Message
{
get
{
return _message;
}
set
{
// Trigger INotifyPropertyChanged
Set("Message", ref _message, value);
}
}
...
private async void loadMessage()
{
// Get the message from SQLite database by Id
var message = await newMessage(messageId);
Message = new MessageViewModel(message);
}
The functions newMessages and newMessage(uint messageId) return new Message objects from the database.
I normally use the INotifyPropertyChanged implementation, but this doesn't work. I query the database 2 times, once for the list (View 1), and once for the edit page (View 2). The SQLite returns two different copies of the Message object, therefore the INotifyPropertyChanged meganism will not work (Only in the current page, not the pages in the backstack).
I could fix the problem by re-using the Message item from the list, but I cannot always do this in all views.
Is there a way to make the INotifyPropertyChanged work in this scenario? Or do I need a different approach to update the values?
You need a different approach. IMHO, the best thing would be to separate the data model from the view model. So have a MessageVm that binds to the WPF view, and put the logic into it to update itself appropriately (along with firing any necessary INotifyPropertyChanged's) from the Message objects that get passed into it.
There are some automatic mapping tools like Automapper which might, to some degree, alleviate the pain that comes along with this approach.
But you really should separate the view model from the data model, the decoupling of these layers is one of the principle tenets of WPF programming.
Hello Stackoverflow community!
I've started recently building a flight dashboard, including Bing Maps WPF Control in MVVM patters (at least as much, as possible) for Windows PC devices.
After seeking the web for a while I was able to deliver CredentialsProvider based on the key in app.config and moved to implement automatic centering of the Bing's control map based on the current device's position.
The XAML:
<m:Map ZoomLevel="16" Mode="Aerial" CredentialsProvider="{Binding BingMapsCredentials}" Grid.Row="1" Grid.ColumnSpan="2" Center="{Binding DevPosition}"/>
The ViewModel:
private readonly CredentialsProvider bingMapsCredentials = new ApplicationIdCredentialsProvider(ConfigurationSettings.AppSettings.Get("BingMapsKey"));
private readonly double nDefaultLatitude = double.Parse(ConfigurationSettings.AppSettings.Get("DefaultLongitude"), System.Globalization.CultureInfo.InvariantCulture);
private readonly double nDefaultLongitude = double.Parse(ConfigurationSettings.AppSettings.Get("DefaultLatitude"), System.Globalization.CultureInfo.InvariantCulture);
private GeoCoordinate nDevicePosition;
public CredentialsProvider BingMapsCredentials
{
get { return bingMapsCredentials; }
}
public Location DevPosition
{
get { return new Location(nDeviceLat, nDeviceLon); }
}
public double nDeviceLon
{
get
{
if (nDevicePosition.IsUnknown)
return nDefaultLongitude;
else
return nDevicePosition.Longitude; }
set { nDevicePosition.Longitude = value; }
}
public double nDeviceLat
{
get
{
if (nDevicePosition.IsUnknown)
return nDefaultLatitude;
else
return nDevicePosition.Latitude;
}
set { nDevicePosition.Latitude = value; }
}
While binding the CredentialsProvider works fine, setting the Center location does not at all. The map is displayed correctly but somewhere in the middle of nowhere. The debugger shows that there is no call on the Get property on location. There are also no WPF warning/error traces in the Output Window.
Am I missing something here?
Any help is appreciated.
P.
You haven't bound nDeviceLat and nDeviceLon to the DevPosition. As such this will only pull in the correct values when the get method is called. If the position is moved there is nothing to trigger the UI to update. Try doing something like this:
public Location DevPosition { get; set; }
public double nDeviceLon
{
get
{
if (nDevicePosition.IsUnknown)
return nDefaultLongitude;
else
return nDevicePosition.Longitude; }
set
{
nDevicePosition.Longitude = value;
DevPosition = new Location(nDeviceLat, nDeviceLon);
}
}
public double nDeviceLat
{
get
{
if (nDevicePosition.IsUnknown)
return nDefaultLatitude;
else
return nDevicePosition.Latitude;
}
set {
nDevicePosition.Latitude = value;
DevPosition = new Location(nDeviceLat, nDeviceLon);
}
}
This seems to be a common problem. There's a couple issues here, but basically you want to use setView() rather than setting to Center.
Here's two excellent articles on working with Bing maps in WPF, which also explains the center property more:
http://www.mobilemotion.eu/?p=1077&lang=en
http://visualstudiomagazine.com/Articles/2012/04/01/Map-Your-Apps.aspx?Page=2
Relates to/Possible Duplicate of:
Center and Zoom on Bing Maps WPF
Hope this helps!
I am using MVVM (prism) to develop wpf application.
One of my model class "StandardContact" has its properties directly bound to the view. I use IDataErrorInfo to track and notify whether the model has any error. If there are any errors in Model, I disable the "Save" Command.
As the user enters some data, I use the StandardContact.PropertyChanged handler to see if "Save" command can execute (i.e if the model data entered by user is valid). The problem is that the StandardContact.PropertyChanged handler is called before the IDataErrorInfo's validation code, so CanExecute for "Save" command does not correctly reflect whether the command can be executed or not. What I am looking for is that, before the CanExecute executes, the IDataErrorInfo validation should run so that the CanExecute will query on the latest data in model and decide whether it is enabled or not. Here is the sample code that I am using
Model:
public class StandardContact :EntityBase, IDataErrorInfo
{
public virtual string Name
{
get { return _name; }
set { SetField(ref _name, value, () => Name); }
}
//...
//Validators
public string this[string propertyName]
{
get
{
string error = null;
//....
}
ViewModel
public class SContactEditViewModel : NotificationObject, INavigationAware
{
//....
StandardContact.PropertyChanged +=
new PropertyChangedEventHandler(StandardContact_PropertyChanged);
void StandardContact_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Requery if command can execute
SaveNewCommand.RaiseCanExecuteChanged();
}
}
I just inspected our priprietary MVVM library. Inside the ViewModels indexer (in your case this is the Models indexer) the requested Property is validated:
public string this[string propertyName]
{
get
{
string result = null;
if (CanDataErrorValidated(propertyName))
{
int errorCount = CurrentValidationAdapter.ErrorCount();
result = ValidateProperty(propertyName, GetValidateValue(propertyName));
// if the error flag has been changed after validation
if (errorCount != CurrentValidationAdapter.ErrorCount())
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
}
else
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
return result;
}
}
So the solution of your problem seems to validate the requested property on the fly.
I don't use prism, but if it exposes some sort of IsValid method or property you can use that to trigger your error checking. And if it doesn't you can write your own.
The basic idea without prism is to have to leverage IDataErrorInfo.Error by doing
bool IsValid{ get{return string.IsNullOrEmpty(Error) } // trigger validation
Then inside your Save.CanExecute method
return IsValid; // trigger validation on demand
HTH,
Berryl