Binding one enum - c#

I have an enum with each value representing an image, for example:
public enum AnimalImages
{
Cow,
Cat,
Dog
}
I also have a converter that takes the enum and returns an ImageSource:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ResourceManager.Instance.GetImageFromEnum((AnimalImages)value);
}
I am using WPF with MVVM; now, I want to add an image to my view. Using the Cow enum, I would like the cow image. I know that I can add a property to my ViewModel and call it like this:
public AnimalImages CowImage
{
return AnimalImages.Cow;
}
and then bind it to my UI. But, I'm thinking there is a better way of doing this. Something like the following (which doesn't work of course):
<Image Source="{x:Static images:AnimalImages.Cow}, Converter={StaticResource AnimalImagesImageBitmapSource}}"/>
any suggestions?

Answer
That is the way to do it if the ViewModel property is an enum. It would look like this:
<!-- DataContext of the Image has to be the ViewModel -->
<Image Source="{Binding CowImage, Converter={StaticResource AnimalImagesImageBitmapSource}}"/>
Alternative
You could also do it so that your ViewModel property CowImage actually returns a URI or an ImageSource of the image, that way you don't need the converter and it looks "cleaner".
Like this:
ViewModel.cs
public ImageSource CowImage
{
get
{
return ResourceManager.Instance.GetImageFromEnum(AnimalImages.Cow);
}
}
Xaml File
<Image Source="{Binding CowImage}"/>

You are almost there. Just need to use Binding and pass static value as Source.
<Image Source="{Binding Source={x:Static images:AnimalImages.Cow},
Converter={StaticResource AnimalImagesImageBitmapSource}}"/>

Related

How to add binding property to Label with converter BoolToObjectConverter

I've the next converter code like the example in this post Xamarin.Forms Binding Converter but when the value come in method only have the property Path="DeviceHasScanner"
and the Binding property never is called :(
public class BoolToObjectConverter<T> : IValueConverter
{
public T TrueObject { set; get; }
public T FalseObject { set; get; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? TrueObject : FalseObject;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((T)value).Equals(TrueObject);
}
}
And I'm try implement it on this way:
<Image HorizontalOptions="Center" WidthRequest="200">
<Image.Source>
<Binding Source="{Binding DeviceHasScanner}">
<Binding.Converter>
<converters:BoolToObjectConverter x:TypeArguments="x:String" TrueObject="presstoscan.png" FalseObject="scanwithcamera.png" />
</Binding.Converter>
</Binding>
</Image.Source>
</Image>
And the boolean binding property in viewmodel is:
public bool DeviceHasScanner
{
get
{
return Settings.Get.DeviceHasScanner; //This code return true or false
}
}
What is the correct way to implement it?
This is not something that you should be using a converter for tbh, what you should be doing instead is assigning your ImageSource through a boolean or keeping the condition for this in your VM where this is directly handled. Converters are usually used in scenarios where you have value conversions.
But if you still insist on using this approach then there are some basic changes that you will need to do first of all you are misunderstanding what Source means here,
<Binding Source="{Binding DeviceHasScanner}">
Source in the above code does not mean the property which you need to provide but the context in which you wanna perform the lookup, that is the reason when you see the object it just copy-pastes the name you have as binding here, Now Source is only required if your Converter should be looking up in a particular context. I am pretty sure this is not the case here so all you need to do is give the Path property the value of your Binding.
So this <Binding Source="{Binding DeviceHasScanner}"> would become something like
<Binding Path="DeviceHasScanner"/>
Now here if your Source is in the same Binding Context you won't need to do anything but if it's not then you need to provide the reference of your BindingContext as shown in the example you can find https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/converters#binding-converter-properties
Good luck!!

How to format a string in XAML without changing viewmodel's property getter?

I have in my application the following interface:
public interface IContactMedium
{
string ContactString { get; set; }
string Type { get; set;}
bool IsValid();
}
This interface is for objects that represent some sort of contact for a person. It could be a phone, email, etc. The ContactString property is the actual contact data (for a phone, for example, it would be the phone number), and the Type is for differentiation in case a person has more than one (for phone, a person can have a Home phone, a Work phone, Cell phone, etc.) The IsValid method is a validation mechanism for each different type of contact medium.
so, let's say I have two objects in my application - Email and Phone - both implement the interface. I'm going to make in the application a UserControl that holds a UI that manages a list of such objects. So the viewmodel would look something like this:
public class ContactsCollectionViewModel<T> : ViewModelBase where T : class, IContactMedium
{
private ObservableCollection<T> _itemsCollection;
public ContactCollectionViewModel(ObservableCollection<T> items)
{
ItemsCollection = items;
}
public ObservableCollection<T> ItemsCollection
{
get { return _itemsCollection; }
set
{
if (_itemsCollection != value)
{
_itemsCollection = value;
OnPropertyChanged(() => ItemsCollection);
}
}
}
}
I want to add to the IContactMedium interface another property/method that provides proper formatting for the ContactString property when used in Binding in WPF. The idea is that the format in the text box bound to ContactString differs depending on the concrete object that is actually stored in the collection:
<TextBox x:Name="ContactString"
Text="{Binding ContactString, StringFormat=???}" />
I searched online a solution for this and couldn't find anything. I saw people suggesting modifying the ContactString property so the getter returns a formatted value. So, for the Phone object, for example, the property would look like this:
public string ContactString
{
get
{
return string.Format("({0}) {1}-{2}", _contactString.Substring(0,3), _contactString.Substring(4,3), _contactString.Substring(7,3));
}
set {
_contactString = value;
}
}
However, this is not a good solution for me. The information is not only used by the UI. It is also sent to other parts of the application, including a database, that need the phone number in its raw form: ##########.
Is there a way to provide the XAML a formatter to use in the StringFormat attribute of the binding? Can the formatting be dictated by the object that implement the interface? If yes, what type does it need to be, and how can I make it accessible to the Binding in XAML?
Can the formatting be dictated by the object that implement the interface?
In Xaml one can provide data templates which are associated with a specific class.
Simply provide the structure in the template with a formatting on the binding to the target property as shown below:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type c:Ship}">
<TextBlock Text="{Binding Path=Name, StringFormat=Ship: {0}}"
Foreground="Red" />
</DataTemplate>
<DataTemplate DataType="{x:Type c:Passage}">
<TextBlock Text="{Binding Path=Name, StringFormat=Passage: {0}}"
Foreground="Blue" />
</DataTemplate>
</Grid.Resources>
<ListBox Name="myListBox"
Height="300"
Width="200"
ItemsSource="{Binding OBSCollection}">
</ListBox>
</Grid>
So for my collection where both class instances of Ship and Passage adhere to ITreeEntity:
public ObservableCollection<ITreeEntity> OBSCollection ...
When bound creates a list where the binding has a specific string format as such:
Note in setting up the data the ships were added first followed by the passages. Xaml is not ordering them in anyway.
Need to list different types objects in one ListBox from a composite collection? See my answers here:
Composite Collection ListBox Answer
Basic example of Listbox & Templates
The thing is that each concrete class that implements the interface would have different formatting rules
Can the formatting be dictated by the object that implement the interface?
The dilema is, whether to add the formating logic to your business objects (IContactMedium implementations) or to presentation layer.
if it is business logic, then yes, you should add the formatting code to your business object.
But most probably it is presentation logic. In that case, either create DataTemplate foreach implementation of the IContactMedium, or create converter. In the converter you can choose correct formatting based on the value type. If the output is just plain text, use converter. If its more that plain text, e.g formatted text, use datatemplates.
TIP: You can use unit tests to test if all implementations of IContactMedium have its DataTemplate or are covered by the converter.
You can use converters. Keep your property simple.
public string ContactString { get; set; }
Implement converter
class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
contactString = value as string;
if(contactString == null)
{
throw new InvalidArgumentException();
}
return string.Format("({0}) {1}-{2}",
contactString.Substring(0,3), contactString.Substring(4,3),
contactString.Substring(7,3));
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Add it as resource
<Window.Resources>
<local:MyConverter x:Key="MyConverter"/>
</Window.Resources>
Use it
<TextBox x:Name="ContactString"
Text="{Binding ContactString, Converter{StaticResource MyConverter}}" />
You can simply override the ToString() method. By default, a ListBox will use the object's ToString() method as the display text for the item.
public override string ToString()
{
return string.Format("({0}) {1}-{2}", _contactString.Substring(0,3), _contactString.Substring(4,3), _contactString.Substring(7,3));
}
This means you don't have to do anything fancy in the ListBox, like defining a DataTemplate, as the ListBox will pick up the formatted string automatically.
<ListBox Name="myListBox"
Height="300"
Width="200"
ItemsSource="{Binding OBSCollection}"/>

System.Linq.GroupBy Key not binding in silverlight

list.ItemsSource=db.Templates.GroupBy(t=>t.CategoryName);
in xaml:
<DataTemplate>
<TextBlock Text="{Binding Key}" />
</DataTemplate>
After this code. Don't show any text in TextBlock. I'm changing Text binding like this
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
TextBlock Text shown like this System.Linq.Lookup^2+Grouping[System.String,Model.Template]
I'm debugging and checking Key property. this is not null.
Why Key don't bind in TextBlock?
How to show group title in Textblock?
Hmmm - unfortunate. The reason is because the result of the GroupBy() call is an instance of System.Linq.Lookup<,>.Grouping. Grouping is a nested class of the Lookup<,> class, however Grouping is marked as internal.
Security restrictions in Silverlight don't let you bind to properties defined on non-public types, even if those properties are declared in a public interface which the class implements. The fact that the object instance you are binding to is of a non-public concrete type means that you can only bind to public properties defined on any public base classes of that type.
You could build a public shim class to act as a view model for the grouping:
public class MyGrouping {
public string Key {get; internal set;}
}
list.ItemsSource=db.Templates.GroupBy(t=>t.CategoryName)
.Select(g => new MyGrouping { Key = g.Key });
It's been a while, but I had similar problem recently, so I decided to post another solution.
You can create a converter and return the value of Key from it
public class GroupNameToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var grouping = (IGrouping<string, [YOUR CLASS NAME]>) value;
return grouping.Key;
}
}
and in Xaml you don't bind to Key, but to the grouping itself.
<TextBlock Text="{Binding Converter={StaticResource groupNameToStringConverter}}" />

Binding Buttons' style to ViewModel property in WP7

I have a play button in a AudioRecord View.
Currently it is declered as:
<Button Width="72" Height="72" Style="{StaticResource RoundPlay}"
DataContext="{Binding ElementName=this, Path=DataContext}"
cmd:ButtonBaseExtensions.Command="{Binding PlayStopCommand}"
/>
When a user clicks the button, a PlayStopCommand in items ViewModel gets executed. I want the button to get its' style set to "RoundStop" whenever the sound is playing.
How can I bind the buttons' Style to a property in my ViewModel (what property type should I use), so that the look of the button is controllable from code?
I have RoundStop style defined, I just need a way to apply it to a button from code.
You should define the playing state in you viewmodel (Playing/Stopped), and bind Button.Style to that property using a converter. In your converter, return a different style (taken from App.Current.Resources) based on the current state.
Edit:
Here's an example of your converter should look like:
public class StateStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (PlaybackState)value == PlaybackState.Playing ? App.Current.Resources["RoundPlay"] : App.Current.Resources["RoundStop"];
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In this example, PlaybackState is an enum:
public enum PlaybackState
{
Playing,
Stopped
}
Then you should add the state property to your view model (The part where you notify the change depends on the framework you are using for MVVM):
private PlaybackState state;
public PlaybackState State
{
get { return state; }
set
{
state = value;
RaiseNotifyPropertyChanged("State");
}
}
Declare your converter in XAML:
<UserControl.Resources>
<converters:StateStyleConverter x:Key="StateStyleConverter"/>
</UserControl.Resources>
And finally bind it to the button:
<Button Width="72" Height="72" Style="{Binding State, Converter={StaticResource StateStyleConverter}}"
DataContext="{Binding ElementName=this, Path=DataContext}"
cmd:ButtonBaseExtensions.Command="{Binding PlayStopCommand}"
/>
You could use a ToggleButton and make the necessary visual changes in the visual states for checked/unchecked.
If you must do it the way your question states, then you can define the Style in the resources and then access it in the code-behind from this.Resources["YourStyleKey"]; Your problem will be getting it from the view to the view model, hence my first suggestion :)

Anyway to set 'DataContext' of a row shared by ListView Columns?

That's the best way I could think of to phrase my question, here is the scenario: I have a ListView bound to a collection of objects. Each of those objects has a property UserID which is just a reference ID to a User object. In my ListView I wish to display multiple properties from both the object and the User. To do this I have created a class that implements MultiValueConverter to serve as a lookup table for the user objects. So I use a multibinding which passes the value converter the UserID and a dictionary look up table which is exposed by the underlying ViewModel.
This all works fine and dandy except I am hoping there is a way I could set the DataContext or something of the 'row' that the ListView columns share. In this way I could change my value converter to just return a User object instead of specific properties of the user object. And then I could just bind to the properties of that DataContext. I don't want to create a new value converter for each User property I wish to expose. The only other way I can think of to do this is by passing property names to value converter and using reflection.
Any ideas? I realize that this DataContext I am dreaming of is the job of the dataobjects bound to the ListView's ItemsSource, but perhaps there is something else I could use too. Attached Properties seem to solve every WPF problem I have so I am betting the solution would have to do with using an AttachedProperty to create this 'datacontext'
I'm sure someone will tell me to expose the User object from the dataobjects themselves instead of using some backwards method of using user ids and lookup table, BUT, I am doing this for a reason. thanks.
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.Header>User</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock MinWidth="120">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource UserIDConverter}">
<Binding Path="UserID" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=UserControl}" Path="DataContext.Users"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
The converter:
public class UserIDConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
int userId = (int)values[0];
IDictionary<int, PhoneUser> table = values[1] as IDictionary<int, PhoneUser>;
if (table.ContainsKey(userId))
{
PhoneUser user = table[userId];
return user.LastName;
//I'd like to just return user !!
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
So, if I understand you correctly, you'd like your converter to just return an entire PhoneUser object, then have each column decide which property of PhoneUser to grab?
If you're really going to insist on this convoluted method, I think your reflection idea (pass the property name into the converter and use reflection to return the value) would be best.
That said, I can't resist giving the answer you didn't want to hear (even if it doesn't help you, it might help someone else). Here's what I'd really recommend you do...
Create a class that combines your current object (say it's called Foo) and a PhoneUser.
public class FooPhoneUser
{
Foo Foo { get; set; }
PhoneUser User { get; set; }
}
Use LINQ to combine these two classes together:
var FooPhoneUsers =
from
f in Foos
join
pu in PhoneUsers on f.UserId equals pu.Id
select
new FooPhoneUser { Foo = f, User = pu };
Get rid of all that binding markup from your GridViewColumn, and just put something like this:
<TextBlock MinWidth="120" Text={Binding User.LastName} />
or
<TextBlock MinWidth="120" Text={Binding Foo.SomeOtherProp} />
It would be much easier if you could populate your data object with PhoneUser, instead of just the ID, then you could do:
<DataTemplate>
<StackPanel>
<TextBlock MinWidth="120" Text="{Binding Path="User.FirstName}">
</TextBlock>
<TextBlock MinWidth="120" Text="{Binding Path="User.LastName}">
</TextBlock>
</StackPanel>
</DataTemplate>
The class structure would look something like this:
public class myDataObject //The data object you already have.
{
public string value1;
public string value2;
public PhoneUser User; //currently you have "UserID" here.
}
public class PhoneUser
{
public string FirstName;
public string LastName;
}
If it does not suit you to retrieve all user data when the data object is first loaded, you could use a "Lazy Loading" strategy, like this:
public class myDataObject //The data object you already have.
{
public string UserID;
public string value2;
private PhoneUser _User;
public PhoneUser User
{
get
{
if(_User==null)
_User = getUserFromDatabase(UserID);
return _User;
}
}
}
I believe you could do this without any changes to the structure of your code.

Categories