I know that i can hide anything in codebehind, in selectionchanged event handler. But is it possible to, lets say, to have 2 PivotItems and one control outside of pivot, and hide that control, when 1st PivotItem is selected in xaml?
Worked, thanks to #Josh Earl, using the converter:
public class PivotIndexToVisibilityConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
int index = (int)value;
return index == 0 ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
Visibility visibility = ( Visibility )value;
return visibility == Visibility.Visible ? 0 : 1;
}
}
I don't think its possible to do this directly. You could get pretty close, though, if you databound your Visibility property to the PivotItem.SelectedItem property. You'd need to create a simple ValueConverter to translate your PivotItem index to a Visibility.Collapsed or Visibility.Visible as appropriate.
Here's a good intro to ValueConverter.
Related
In the ViewModel I have a dependency property called IsButtonVisible, either true or false.
In the View layer I have this layout that I need to show or hide, depending on the value of IsButtonVisible. But I don't just want to show or hide it abruptly, I want to smoothly fade it in and out.
I read online that the way to do it is via events, e.g. to fade the control to 0% or 100% over 3 seconds:
await image.FadeTo(0, 3000);
await image.FadeTo(1, 3000);
But now I want to do it via databinding. The old code was:
MyControl.SetBinding(IsVisibleProperty, "IsButtonVisible");
Now I need to use smooth opacity instead, the farthest I could reach is:
MyControl.SetBinding(OpacityProperty, "IsButtonVisible", BindingMode.OneWay, new MyButtonConverter()););
public class MyButtonConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
return 1; // 100%; visible
return 0; // 0%; invisible
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Problem is it's as abrupt as visibility is involved.
Is there a way I can use animation somewhere, whether in the databinding statement or inside the converter?
Thanks.
You do not what to add the View (animation) logic inside your ViewModel as that will break its independance of the ContentPage/View/..., testing becomes difficult, etc, ... (lots of SO Q/As regarding this already).
So lets assume that your ViewModel exposes a property change event, an assignable callback Command or Action, or a System.Reactive subject (my personal choice), etc... for your IsButtonVisible property that you can attach to in your View (not the ViewModel), something like this in the ContentPage .ator:
InitializeComponent();
BindingContext = viewModel = new AnimPageViewModel();
viewModel.PropertyChanged += ViewModel_PropertyChanged;
Now when the IsButtonVisible changes you can run your animation, this example just toggles the Opacity back and forth each time that property changes.
async void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsButtonVisible")
{
await animateButton
.FadeTo(
Math.Abs(animateButton.Opacity) > double.Epsilon ? 0 : 1,
2000,
Math.Abs(animateButton.Opacity) < double.Epsilon ? Easing.CubicIn : Easing.CubicOut);
}
}
I am using VS2013 and creating a windows store app (windows 8.1) from the templates. Many references show that there is a BooleanToVisibilityConverter class being created in the common folder that can be referenced it the xaml but the class is not created in my common folder. I get NavigationHelper, ObservableDictionary, RelayCommand, and SuspensionManager but not BooleanToVisibilityConverter.
So what am I missing about this?
Templates can be changed from version to version. So, create it by hands:
public sealed class BooleanToVisibilityConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, string language )
{
return ( value is bool && ( bool )value ) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack( object value, Type targetType, object parameter, string language )
{
return value is Visibility && ( Visibility )value == Visibility.Visible;
}
}
I'm attempting to validate the input of a text box which corresponds to a property of type double in my model. If the user inputs "foo" I want to know about it so I can display an error. However, the ValueProvider is dropping the value silently (no errors are added to the ModelState).
In a normal submission, I fill in "2" for the text box corresponding to myDouble and submit the form.
Inspecting controllerContext.HttpContext.Request.Form shows that myDouble=2, among other correct inputs. bindingContext.ValueProvider.GetValue("myDouble") == 2, as expected. The bindingContext.ModelState.Count == 6 and bindingContext.ModelState["myDouble"].Errors.Count == 0. Everything is good and the model binds as expected.
Then I fill in "foo" for the text box corresponding to myDouble and submitted the form.
Inspecting controllerContext.HttpContext.Request.Form shows that myDouble=foo, which is what I expected. However, bindingContext.ValueProvider.GetValue("myDouble") == null and bindingContext.ModelState.Count == 5 (The exact number isn't important, but it's one less than before). Looking at the ValueProvider, is as if myDouble was never submitted and the model binding occurs as if it wasn't. This makes it difficult to differentiate between a bad input and no input.
Is this the expected behavior of ValueProvider? Is there a way to get ValueProvider to report when conversion fails without implementing a custom ValueProvider? Thanks!
Part of the problem here is that your model has a type of double.
The problem is that double cannot be null, and as such will default to a value of 0, thus on submit.. if the ValueProvider returns null, the value of the field will still be 0 and validation will pass.
You should make the double nullable, by using double? and then add a Required attribute to the property. If the type is not required, then you can add a regular expression validator.
You can implement custom model binding logic using by implementing IModelBinder. This will put the data validation logic at the model binding level - thus being usable for any type of ValueProvider. In your situation, the model binder would determine that when myDouble = "foo" is not a double and add an exception to the ModelState errors showing the invalid value.
public class CustomDoubleBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
decimal tempDouble = 0m;
if (bindingContext.ValueProvider.GetValue(bindingContext.ModelName) != null)
{
if (double.TryParse(bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue, out tempDecimal))
{
bindingContext.ModelState[bindingContext.ModelName].Errors.Add("Error parsing double value: " + bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue);
}
}
return tempDouble;
}
}
Having created this custom model binder, you will then need to register it in the Global.asax:
protected void Application_Start()
{
ModelBinders.Binders[typeof(double)] = new CustomDoubleBinder();
}
What is the best way to do this for a WP7 7.0 app (SL3). Should I use visual states? IS there a way to use attributes from the model sort of like the Silverlight examples would do ? Any good examples with MVVM+WP7?
So far all I can find is these two examples.
http://babaandthepigman.wordpress.com/2010/10/21/simple-textbox-validation-wp7/
http://www.windowsphonegeek.com/articles/Building-WP7-Custom-Validation-Control---Architecture-amp-Basic-Prototype
For every view you should to make files named Validated(ViewName).cs. Everyone should inherit from ValidatedModelBase implemented interfaces INotifyPropertyChanged, INotifiDataError.
Properties of ValidationModelBase:
IsValid
ShowErrors
Methods:
T ToModel - convert all ValidatedModel fields to ViewModel
void Validate:
public void Validate()
{
var fields = new List<string>();
var type = this.GetType();
var baseType = typeof(ValidatedModelBase<T>);
while (type != baseType)
{
fields.AddRange(type.GetFields()
.Where(field => field.FieldType == typeof(String) && field.Name.EndsWith("PropertyName"))
.Select(field => field.GetValue(this) as String));
type = type.BaseType;
}
foreach (var field in fields)
{
this.SetErrors(field, this.Validate(field), false);
}
this.RefreshIsValid();
}
virtual IList Validate(string propertyName) - it will be implemented in ValidatedModels, where You give property name to validate as parameters and handle it in simple switch/case instruction.
I can write more about my method, if You want.
Here's a scenario:
I have an autocomplete plugin (custom) that keeps a hidden field of JSON objects (using a specific struct).
I've created an Html helper that helps me easily bind to a specific custom model (basically, it has a JSON property that is for two-way binding and a property that lets me deserialize the JSON into the appropriate struct):
public class AutoCompleteModel {
public string JSON { get; set; }
public IEnumerable<Person> People {
get {
return new JavaScriptSerializer().Deserialize<Person>(this.JSON);
}
set {
this.JSON = new JavaScriptSerializer().Serialize(value);
}
}
}
This works great and I can model bind using the default binder #Html.Autocomplete(viewModel => viewModel.AutoCompleteModelTest). The HTML helper generates HTML like:
<input type="text" id="AutoCompleteModelTest_ac" name="AutoCompleteModelTest_ac" value="" />
<input type="hidden" id="AutoCompleteModelTest_JSON" name="AutoCompleteModelTest.JSON" value="{JSON}" />
The problem is this is not the best way for consumers. They have to manually set the People property to an array of Person structs. In my data layer, my domain objects probably will not be storing the full struct, only the person's ID (a corporate ID). The autocomplete will take care of looking up the person itself if only given an ID.
The best scenario will be to call it like this:
#Html.Autocomplete(domainObject => domainObject.PersonID) or
#Html.Autocomplete(domainObject => domainObject.ListOfPersonIDs
I would like it to work against the string property AND against the custom AutoCompleteModel. The autocompleter only updates a single hidden field, and that field name is passed back on postback (the value looks like: [{ "Id":"12345", "FullName":"A Name"},{ "Id":"12347", "FullName":"Another Name" }]).
The problem is, of course, that those domain object properties only have an ID or array of IDs, not a full Person struct (so cannot be directly serialized into JSON). In the HTML helper, I can transform those property values into a struct, but I don't know how to transform it back into a simple type on POST. The solution I need would transform an ID into a new Person struct on page load, serializing it into the hidden field. On POST, it would deserialize the generated JSON back into a simple array of IDs.
Is a custom model binder the solution I need? How can I tell it to work both with a custom model AND simple types (because I don't want it applied to EVERY string property, just need it to deal with the values given by the HTML helper).
I figured it out, it's possible!
To clarify, I needed to: transform a string or string array (of IDs) into a JSON structure for my hidden field value, then on post back, deserialize the JSON in the hidden field and transform the struct back into a simple string or string array (of IDs) for my domain object's property.
Step 1: Create a HTML helper
I had done this already, but only for accepting my custom AutoCompleteModel type. I needed one for a string and an Enumerable of string type.
All I did was generate my Person struct(s) from the value of the property and serialize them into JSON for the hidden field the Autocompleter uses (this is an example of the string helper, I also have a nearly identical one for IEnumerable<string>):
public static MvcHtmlString AutoComplete<TModel>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, string>> idProp)
where TModel : class
{
TModel model = htmlHelper.ViewData.Model;
string id = idProp.Compile().Invoke(model);
string propertyName = idProp.GetPropertyName();
Person[] people = new Person[] {
new Person() { ID = id }
};
// Don't name the textbox the same name as the property,
// otherwise the value will be whatever the textbox is,
// if you care.
MvcHtmlString textBox = htmlHelper.TextBox(propertyName + "_ac", string.Empty);
// For me, the JSON is the value I want to postback
MvcHtmlString hidden = htmlHelper.Hidden(propertyName, new JavaScriptSerializer().Serialize(people));
return MvcHtmlString.Create(
"<span class=\"AutoComplete\">" +
textBox.ToHtmlString() +
hidden.ToHtmlString() +
"</span>");
}
Usage: #Html.AutoComplete(model => model.ID)
Step 2: Create a custom model binder
The crux of my issue was that I needed this binder to only apply to certain properties, and they were strings or string arrays.
I was inspired by this article because it used Generics. I decided, hey, we can just ask people what property they want to apply the binder for.
public class AutoCompleteBinder<T> : DefaultModelBinder
where T : class
{
private IEnumerable<string> PropertyNames { get; set; }
public AutoCompleteBinder(params Expression<Func<T, object>>[] idProperties)
{
this.PropertyNames = idProperties.Select(x => x.GetPropertyName());
}
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
var submittedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (submittedValue != null && this.PropertyNames.Contains(propertyDescriptor.Name))
{
string json = submittedValue.AttemptedValue;
Person[] people = new JavaScriptSerializer().Deserialize<Person[]>(json);
if (people != null && people.Any())
{
string[] IDs = people.Where(x => !string.IsNullOrEmpty(x.ID)).Select(x => x.ID).ToArray();
bool isArray = bindingContext.ModelType != typeof(string) &&
(bindingContext.ModelType == typeof(string[]) ||
bindingContext.ModelType.HasInterface<IEnumerable>());
if (IDs.Count() == 1 && !isArray)
return IDs.First(); // return string
else if (IDs.Count() > 0 && isArray)
return IDs.ToArray(); // return string[]
else
return null;
}
else
{
return null;
}
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
GetPropertyName() (translate LINQ expression into a string, i.e. m => m.ID = ID) and HasInterface() are just two utility methods I have.
Step 3: Register
Register the binder on your domain objects and their properties in Application_Start:
ModelBinders.Binders.Add(typeof(Employee), new AutoCompleteBinder<Employee>(e => e.ID, e => e.TeamIDs));
It's only a little bit annoying to have to register the binder for specific properties, but it's not the end of the world and provides a nice, smooth experience working with my autocompleter.
Any comments are welcome.