I need to display a number as binary string (e.g. 8 => 1000). Sure I can convert it using BitConverter and set the text of my TextBox on my own in the code behind file. But this looks somewhat ugly. Is it possible to bind the TextBox to some source and convert it automatically?
I would suggest to use a ValueConverter
Create a class like this:
public class BinaryConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return System.Convert.ToString(Convert.ToInt32(Convert.ToDouble(value)), 2);
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
Then you can use it like this (without any code behind)
<Window.Resources>
<local:BinaryConverter x:Key="binConverter"></local:BinaryConverter>
</Window.Resources>
<StackPanel>
<Slider Name="sli" Minimum="0" Maximum="255" IsSnapToTickEnabled="True">
</Slider>
<TextBox Text="{Binding ElementName=sli,Path=Value,Mode=OneWay,Converter={StaticResource binConverter}}"></TextBox>
</StackPanel>
Related
If I have a view model like this:
public class MyModel{
public DateTime? StartDate {get;set;}
}
And on a view an input tag is used with an asp-for tag helper like so:
<input asp-for="StartDate" />
The default html that is generated by this is
<input type="datetime" id="StartDate" name="StartDate" value="" />
But what I want it to generate is html that looks like this:
<input type="datetime" id="startDate" name="startDate" value="" />
How can I make the asp-for input tag helper generate camel case names like above without having to make my model properties camelCase?
After studying the code that #Bebben posted and the link provided with it, I continued to dig more into the Asp.Net Core source code. And I found that the designers of the Asp.Net Core provided some extensibility points that could be leveraged to achieve lower camelCase id and name values.
To do it, we need to implement our own IHtmlGenerator which we can do by creating a custom class that inherits from DefaultHtmlGenerator. Then on that class we need to override the GenerateTextBox method to fix the casing. Or alternatively we can override the GenerateInput method to fix the casing of name and id attribute values for all input fields (not just input text fields) which is what I chose to do. As a bonus I also override the GenerateLabel method so the label's for attribute also specifies a value using the custom casing.
Here's the class:
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Text.Encodings.Web;
namespace App.Web {
public class CustomHtmlGenerator : DefaultHtmlGenerator {
public CustomHtmlGenerator(
IAntiforgery antiforgery,
IOptions<MvcViewOptions> optionsAccessor,
IModelMetadataProvider metadataProvider,
IUrlHelperFactory urlHelperFactory,
HtmlEncoder htmlEncoder,
ClientValidatorCache clientValidatorCache) : base
(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory,
htmlEncoder, clientValidatorCache) {
//Nothing to do
}
public CustomHtmlGenerator(
IAntiforgery antiforgery,
IOptions<MvcViewOptions> optionsAccessor,
IModelMetadataProvider metadataProvider,
IUrlHelperFactory urlHelperFactory,
HtmlEncoder htmlEncoder,
ClientValidatorCache clientValidatorCache,
ValidationHtmlAttributeProvider validationAttributeProvider) : base
(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder,
clientValidatorCache, validationAttributeProvider) {
//Nothing to do
}
protected override TagBuilder GenerateInput(
ViewContext viewContext,
InputType inputType,
ModelExplorer modelExplorer,
string expression,
object value,
bool useViewData,
bool isChecked,
bool setId,
bool isExplicitValue,
string format,
IDictionary<string, object> htmlAttributes) {
expression = GetLowerCamelCase(expression);
return base.GenerateInput(viewContext, inputType, modelExplorer, expression, value, useViewData,
isChecked, setId, isExplicitValue, format, htmlAttributes);
}
public override TagBuilder GenerateLabel(
ViewContext viewContext,
ModelExplorer modelExplorer,
string expression,
string labelText,
object htmlAttributes) {
expression = GetLowerCamelCase(expression);
return base.GenerateLabel(viewContext, modelExplorer, expression, labelText, htmlAttributes);
}
private string GetLowerCamelCase(string text) {
if (!string.IsNullOrEmpty(text)) {
if (char.IsUpper(text[0])) {
return char.ToLower(text[0]) + text.Substring(1);
}
}
return text;
}
}
}
Now that we have our CustomHtmlGenerator class we need to register it in the IoC container in place of the DefaultHtmlGenerator. We can do that in the ConfigureServices method of the Startup.cs via the following two lines:
//Replace DefaultHtmlGenerator with CustomHtmlGenerator
services.Remove<IHtmlGenerator, DefaultHtmlGenerator>();
services.AddTransient<IHtmlGenerator, CustomHtmlGenerator>();
Pretty cool. And not only have we solved the id and name casing issue on the input fields but by implementing our own custom IHtmlGenerator, and getting it registered, we have opened the door on all kinds of html customization that can be done.
I'm starting to really appreciate the power of a system built around an IoC, and default classes with virtual methods. The level of customization available with little effort under such an approach is really pretty amazing.
Update
#Gup3rSuR4c pointed out that my services.Remove call must be an extension method that's not included in the framework. I checked, and yep that true. So, here is the code for that extension method:
public static class IServiceCollectionExtensions {
public static void Remove<TServiceType, TImplementationType>(this IServiceCollection services) {
var serviceDescriptor = services.First(s => s.ServiceType == typeof(TServiceType) &&
s.ImplementationType == typeof(TImplementationType));
services.Remove(serviceDescriptor);
}
}
The simplest way to do this is to just write
<input asp-for="StartDate" name="startDate" />
Or do you want to have it generated completely automatically in camel case, for the whole application?
To do that, it seems like you have to implement your own InputTagHelpers in Microsoft.AspNetCore.Mvc.TagHelpers.
Here is the method where the name is generated:
private TagBuilder GenerateTextBox(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
{
var format = Format;
if (string.IsNullOrEmpty(format))
{
format = GetFormat(modelExplorer, inputTypeHint, inputType);
}
var htmlAttributes = new Dictionary<string, object>
{
{ "type", inputType }
};
if (string.Equals(inputType, "file") && string.Equals(inputTypeHint, TemplateRenderer.IEnumerableOfIFormFileName))
{
htmlAttributes["multiple"] = "multiple";
}
return Generator.GenerateTextBox(
ViewContext,
modelExplorer,
For.Name,
value: modelExplorer.Model,
format: format,
htmlAttributes: htmlAttributes);
}
(The above code is from https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs, Apache License, Version 2.0, Copyright .NET Foundation)
The line is "For.Name". The name is sent into some other methods, and the one that in the end gives the final name is in a static class (Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.NameAndIdProvider), so nothing we can really plug into easily.
Windows phone 8 with Caliburn.Micro 2.0.1 cannot bind to LongListSelector. For Caliburn tries to bind items to Visibility property.
Here is XAML
<phone:LongListSelector
x:Name="Items"
ItemTemplate="{StaticResource MyItemTemplate}">
</phone:LongListSelector>
and view model property is pretty basic:
IObservableCollection<Item> _Items;
public IObservableCollection<Item> Items
{
get { return _Items; }
set
{
_Items = value;
NotifyOfPropertyChange(() => Items);
}
}
public class Item : PropertyChangedBase
{
string _Name;
public string Name
{
get { return _Name; }
set { _Name = value; NotifyOfPropertyChange(() => Name); }
}
}
Here is the debug output
System.Windows.Data Error: 'MS.Internal.Data.DynamicValueConverter' converter failed to convert value 'Caliburn.Micro.BindableCollection`1[Checklists.ViewModels.ItemsPageViewModel+Item]' (type 'Caliburn.Micro.BindableCollection`1[CLS.ViewModels.ItemsPageViewModel+Item]'); BindingExpression: Path='Items' DataItem='CLS.ViewModels.ItemsPageViewModel' (HashCode=38524289); target element is 'Microsoft.Phone.Controls.LongListSelector' (Name='Items'); target property is 'Visibility' (type 'System.Windows.Visibility').. System.InvalidOperationException: Can't convert type Caliburn.Micro.BindableCollection`1[CLS.ViewModels.ItemsPageViewModel+Item] to type System.Windows.Visibility.
at MS.Internal.Data.DefaultValueConverter.Create(Type sourceType, Type targetType, Boolean targetToSource)
at MS.Internal.Data.DynamicValueConverter.EnsureConverter(Type sourceType, Type targetType)
at MS.Internal.Data.DynamicValueConverter.Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
at System.Windows.Data.BindingExpression.ConvertToTarget(Object value).
The convention you're trying to make use of here is the one Caliburn.Micro has set up for ItemsControl, unfortunately it doesn't appear that LongListSelector inherits from ItemsControl. It's odd that it doesn't so you'd assume everything would just work.
A very basic convention for LongListSelector could be added with the following
ConventionManager.AddElementConvention<LongListSelector>(LongListSelector.ItemsSourceProperty, "DataContext", "Loaded");
which would be called in your Bootstrapper. Note that this convention does't set up a default ItemTemplate that the one for ItemsSource does.
Edit:
The first property is used for property binding conventions, if you have a property matching the x:Name then this is the property bound to.
The second is the parameter property, if you refer to the element as a parameter in a message such as cm:Message.Attach="DoStuff(Items)" then what property is used.
The third is event that fires the action if there is one attached.
The one mentioned in the comments
ConventionManager.AddElementConvention<LongListSelector>(LongListSelector.Items‌​SourceProperty, "SelectedItem", "SelectionChanged");
is better in that if you're using any of the other features that require the second two properties. The first convention simply uses boiler plate parameters.
which makes sense its not a bug since LongListSelector isn't in the ConventionManager list of controls for windows Phone. You would have to add it as a custom convention for that control. Otherwise just bind normally...
I started using observable ConcurrentDictionary because it's threadsafe. But now I face a problem how to bind it to the list.
local:MvxBind="ItemsSource SourceDictionary"
obviously cannot work because item consists of the KeyValuePair and not the object itself.
local:MvxBind="ItemsSource SourceDictionary.Values"
does not work. Which, I must admit, puzzles me.
I even tried a long shot and did some converter :
public class SourceDictionaryValueConverter : MvxValueConverter<KeyValuePair<string, SourceClass >, SourceClass >
{
protected override SourceClass Convert(KeyValuePair<string, SourceClass> value, Type targetType, object parameter, CultureInfo culture)
{
return value.Value;
}
}
and bind it like this
local:MvxBind="ItemsSource SourceDictionary, Converter=SourceDictionary"
but it didn't work. I suppose it asks for IList.
Is there any way to bind ListView to the Dictionary?
You should be able to bind to a Dictionary - or indeed to any IEnumerable.
If you are binding to the Dictionary itself (rather than to Values) then you will need to write your item bindings so that they know about KeyValuePair - e.g. so you might need to bind to Key.Name rather than to just Name.
However, Dictionary's themselves do not notify listeners of changes - so using a Dictionary is like using a List - not like using an ObservableCollection. If you want this type of behaviour then you will need to write or find a Dictionary class which implements INotifyCollectionChanged.
I have applications on WP8, it contains images loaded from the server. Pictures are changing on server but have the same name and the same url. After the change, the phone still displays the previous pictures. How to solve this problem.
This is due to built in resource caching, which affects both remote requests as well as requests for local image resources in your application. When declaring your image control markup, you can disable caching by creating its source property manually. For instance:
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding ContentPath}"
CreateOptions="IgnoreImageCache" />
</Image.Source>
</Image>
Well, the phone caches the images that are downloaded. If you wanna set a new image you have to "clean" the image source. Your questions is not given me inave information, but from what i understand, one simple converter should do the trick:
public class CacheImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string path = value as string;
Uri imageFileUri = new Uri(path, UriKind.Absolute);
BitmapImage bm = new BitmapImage(imageFileUri);
return bm;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XML
<phone:PhoneApplicationPage.Resources>
<imgConv:CacheImageConverter x:Key="ConvertNew" />
</phone:PhoneApplicationPage.Resources>
...
<Image Source="{Binding strPath, Converter={StaticResource ConvertNew}}"/>
i have this problem: i use the json to send data to server.
All works fine but the problem is a situation like:
public enum SexType
{
Male : 0,
Female : 1
}
class People{
public SexType Sex {get;set;}
}
That create me the json:
{"Sex" : 0}
When i send back to server, this fill the ModelStateError with this issue:
The parameter conversion from type 'System.Int32' to type 'SexType' failed because no type converter can convert between these types.
But if i wrap the value with ' all work well:
{"Sex" : '0'}
Anyone have the same problem?
Tnx for all!
Yes, I got the same problem. The weird problem is that if you sent back:
{"Sex" : 'Male'}
it would deserialize no problem.
To solve the problem, I implemented a custom model binder for enums, leveraging the example found here (slightly modified as there were some errors):
http://eliasbland.wordpress.com/2009/08/08/enumeration-model-binder-for-asp-net-mvc/
namespace yournamespace
{
/// <summary>
/// Generic Custom Model Binder used to properly interpret int representation of enum types from JSON deserialization, including default values
/// </summary>
/// <typeparam name="T">The enum type to apply this Custom Model Binder to</typeparam>
public class EnumBinder<T> : IModelBinder
{
private T DefaultValue { get; set; }
public EnumBinder(T defaultValue)
{
DefaultValue = defaultValue;
}
#region IModelBinder Members
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
return bindingContext.ValueProvider.GetValue(bindingContext.ModelName) == null ? DefaultValue : GetEnumValue(DefaultValue, bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue);
}
#endregion
public static T GetEnumValue<T>(T defaultValue, string value)
{
T enumType = defaultValue;
if ((!String.IsNullOrEmpty(value)) && (Contains(typeof(T), value)))
enumType = (T)Enum.Parse(typeof(T), value, true);
return enumType;
}
public static bool Contains(Type enumType, string value)
{
return Enum.GetNames(enumType).Contains(value, StringComparer.OrdinalIgnoreCase);
}
}
}
and then registering the model binder in global.asax.cs.
In your case it would be something like:
ModelBinders.Binders.Add(typeof(SexType), new EnumBinder<SexType>(SexType.Male));
I am not sure if there is a quicker way, but this works great.
The Model binding uses the Enum.Parse() method, which is fairly smart about interpreting strings but does NOT explicitly cast or convert other types into strings, even if system-level facilities exist to do so and even if they're the internal storage type used within the Enum.
Is this the right behavior? Arguably so, since if you don't know enough to convert your Enum values to strings you might not be aware that the right-hand side of the Enum values are not necessarily unique within the Enum, either.
As a matter of personal taste (and this is probably also because I do way too much statistical analysis programming) for sex I generally prefer to define it as a clear boolean value, i.e. instead of differentiating between arbitrary values for 'Male' and 'Female' I use a variable called e.g. IsFemale and set it to true or false. This plays more nicely with json, since it relies on primitive types common to both languages, and requires less typing when you want to use it.