WPF Dynamic Binding - c#

Today, I was working on a WPF UserControl to display the current value of a few variables. I was wondering if there would be a way to make a super simple property grid in WPF. The problem is on the starred line of the XAML below. How would I bind a string to a property with an ItemTemplate like I have setup below? To be more clear can I embed bindings inside of one another {Binding Path={Binding Value}}.
Here is the class:
public class Food
{
public string Apple { get; set; }
public string Orange { get; set; }
public IEnumerable<KeyValuePair<string, string>> Fields
{
get
{
yield return new KeyValuePair<string, string>("Apple Label", "Apple");
yield return new KeyValuePair<string, string>("Orange Label", "Orange");
}
}
}
And here is the XAML:
<UserControl x:Class="MAAD.Plugins.FRACTIL.Simulation.SimulationStateView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="331" Width="553">
<ListView ItemSource="{Binding Fields}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Key}" />
**<TextBlock Text="{Binding Path={Binding Value}}" />**
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>

The essence of the problem is that you need not just the list of field descriptions and names, but the actual object that has those fields and names.
You can use a converter like that adds target references to the field objects and provides a value accessor, like this:
public class PropertyValueAccessConverter : IMultiValueConverter
{
object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var target = values[0];
var fieldList = values[1] as IEnumerable<KeyValuePair<string,string>>;
return
from pair in fieldList
select new PropertyAccessor
{
Name = pair.Name,
Target = target,
Value = target.GetType().GetProperty(target.Value),
};
}
object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
public class PropertyAccessor
{
public string Name
public object Target;
public PropertyInfo Property;
public object Value
{
get { return Property.GetValue(Target, null); }
set { Property.SetValue(Target, value, null); }
}
}
public static PropertyValueAccessConverter Instance = new PropertyValueAccessConverter();
}
With this converter you can bind your ItemsSource like this:
<ListView>
<ListView.ItemsSource>
<MultiBinding Converter="{x:Static local:PropertyValueAccessConverter.Instance}">
<Binding />
<Binding Path="Fields" />
</MultiBinding>
</ListView.ItemsSource>
</ListView>
By the way, a much more efficient way to implement your Fields property is:
public IEnumerable<KeyValuePair<string, string>> Fields
{
get
{
return new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("Apple Label", "Apple");
new KeyValuePair<string, string>("Orange Label", "Orange");
}
}
}
Though frankly I would use description attributes on the individual properties along with reflection instead of hard-coding the list. That would also eliminate the need for the MultiBinding in the first place.

Well, it should just be:
<TextBlock Text="{Binding Path=Value}" />
However, I think I understand what you're trying to do. The problem is KeyValuePair isn't an INotifyPropertyChange subtype (rightfully so, won't get into that) so you'll never get a notification from it if the value is changed in the dictionary. Also, KeyValuePair is actually a struct. So, changing the value on the bound copy will not update the actual data source because it's a copy of the data.
If the Model you're working with actually is KeyValuePair, you would need to create a more specific View-Model class to enable this databinding scenario. This would need to be some kind of class that wraps the Key and has a reference to the underlying source (probably a Dictionary?) and actually makes the calls to update the value on the underlying source when its property is changed. That said, you still won't get notifications out of a Dictionary (again, assuming that's your source) because it doesn't fire any, so you won't be able to provide forward change notifications.

Related

Is it possible to dynamically change/remove the SwipeItems of a SwipeControl in UWP?

I'm trying to use Microsofts new SwipeControl to allows users to reveal an action that's available to the item. What I'm struggling with is removing the SwipeItems when the action is no longer available, I know it is possible as setting and nulling the LeftItems property on the SwipeControl works as I expect it to, but I need it to be more dynamic than this.
Some notes on setup, I'm using Caliburn Micro as the MVVM framework and its implementation of INotifyPropertyChanged. I'm not using x:Bind.
Attempt 1 - Control based on SwipeControl:
I tried creating a new control based on the SwipeControl. I added DepedencyProps for LeftItemsEnabled and InternalLeftItems. InternalLeftItems is used to store the list of Items between each removal and reset. To remove and reset the LeftItems property I use the LeftItemsEnabled property by changing the boolean. I set the LeftItems to my InternalLeftItems value or to null. See below for code:
Dependency Properties and PropertyChanged implementation
public static readonly DependencyProperty EnableLeftItemsProperty =
DependencyProperty.Register("EnableLeftItems", typeof(bool), typeof(UpdatedSwipeControl), new PropertyMetadata(false, LeftEnabledPropertyChanged));
public static readonly DependencyProperty InternalLeftItemsProperty =
DependencyProperty.Register("InternalLeftItems", typeof(SwipeItems), typeof(UpdatedSwipeControl), new PropertyMetadata(new SwipeItems()));
public SwipeItems InternalLeftItems {
get { return (SwipeItems)GetValue(InternalLeftItemsProperty); }
set { SetValue(InternalLeftItemsProperty, value); }
}
private static void LeftEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var control = d as UpdatedSwipeControl;
if (control == null) return;
control.EnableLeftItems = (bool)e.NewValue;
if ((bool)e.NewValue)
control.RightItems = control.InternalRightItems;
else
control.RightItems = null;
UpdateLayout();
}
Usage in View
<Page.Resources>
<SymbolIconSource x:Key="ButtonIconSource"
Symbol="Cancel" />
<SwipeItems x:Key="Items">
<SwipeItem Text="SWIPE ITEM"
IconSource="{StaticResource ButtonIconSource}"
Background="Red" />
</SwipeItems>
</Page.Resources>
<controls:UpdatedSwipeControl x:Name="swipeControl"
EnableLeftItems={Binding ItemsEnabled}
InternalLeftItems="{StaticResource Items}">
<TextBlock Text="SWIPE" />
</SwipeControl>
Property in ViewModel
private bool itemsEnabled;
public bool ItemsEnabled {
get {
return itemsEnabled;
}
set {
itemsEnabled = value;
NotifyOfPropertyChange(nameof(ItemsEnabled));
}
}
Problem with this attempt is the LeftEnabledPropertyChanged only fired once, when changing from the default value False to True on the initial bind. It doesn't fire again on any of the subsequent NotifyPropertyChanged's. I not sure if I've missed something in my setup of the DependencyProps or the PropertyChangedEvent.
Attempt 2 - Converter on LeftItems prop:
My second attempt was to use a Converter on the Binding of the boolean. I created a simple converter to return either SwipeItems in my resources or null. See below for code:
Converter code
public class ItemsConverter : IValueConverter {
public SwipeItems items { get; set; }
public object Convert(object value, Type targetType, object parameter, string language)
{
return (bool) value ? items : null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Implementation in view
<Page.Resources>
<sampleApp:ItemsConverter x:Key="converter" items="{StaticResource Items}"/>
<SwipeItems x:Key="Items">
<SwipeItem Text="SWIPE ITEM"
IconSource="{StaticResource ButtonIconSource}"
Background="Red" />
</SwipeItems>
</Page>
<SwipeControl x:Name="swipeControl"
LeftItems="{Binding ItemsEnabled, Converter={StaticResource converter}}">
<TextBlock Text="SWIPE" />
</SwipeControl>
Same implementation of the ItemsEnabled Property on the ViewModel.
The problem with this attempt was the converter was never hit and there were no Binding errors.
In summation I know it's possible to set/reset the SwipeItems property via code-behind which works ok in very simple scenarios. But anything more dynamic I can't get to work. Has anyone else run into this issue or have a workaround to this?

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}}" />

Create a Collection with different Data Types and Bind to a List

I am trying to create a collection with different data types to bind to a listbox control in Silverlight and C#.
Is this possible if each of the data types implements an interface?
For example, I have separate objects "Violin", "Guitar", and "Drums", each of which implement the "IMusicalInstrument" interface. Can I then create a List, bind that to a listbox, and alternatively add "Violin", "Guitar", and "Drums" objects to that list?
Yes you can, by using a list that is Generic, have a look at List<T>. You can create a list with some instruments like this:
var instruments = new List<IMusicalInstrument> {
new Drum(),
new Guitar(),
new Violin()
}
and then use instrumentsListBox.DataSource = instruments;
However if you want to make it easy for yourself, tell them to implement the properties for DisplayMember and ValueMember, this is what the ListBox uses to determen what to show and use as value when you select something.
To clarify one thing, because Silverlight data-binding isn't strongly typed, the various items in the list don't need to all support a common interface. It's likely a better design if they do, but all they actually need to do is support the properties to which you'll be binding. And if you want to get fancy, you could divide up your datatemplate into different parts, each of which is designed for a different class, and then hide the parts that aren't applicable to the particular item being displayed. So you could have a completely different interface for a drum, guitar, violin, etc. For instance, let's say your instruments were defined like this:
public class Drum
{
public int DrummerCount { get; set; }
}
public class Violin
{
public int ViolinistCount { get; set; }
}
public class Guitar
{
public int GuitaristCount { get; set; }
}
You could then create a ListBox that looked like this:
<ListBox x:Name="instrumentsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock
Visibility="{Binding Converter={StaticResource instrumentVisibilityConverter}, ConverterParameter=Drum}"
Text="{Binding DrummerCount, StringFormat=Drummers:\{0\}, Converter={StaticResource debugConverter}}"/>
<TextBlock
Visibility="{Binding Converter={StaticResource instrumentVisibilityConverter}, ConverterParameter=Violin}"
Text="{Binding ViolinistCount, StringFormat=Violinists:\{0\}, Converter={StaticResource debugConverter}}"/>
<TextBlock
Visibility="{Binding Converter={StaticResource instrumentVisibilityConverter}, ConverterParameter=Guitar}"
Text="{Binding GuitaristCount, StringFormat=Guitarists:\{0\}, Converter={StaticResource debugConverter}}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note how each class has a separate TextBlock that's used to display it. The visibility of each TextBlock is controlled via an InstrumentVisibilityConverter that looks like this:
public class InstrumentVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string expectedType = parameter as string;
if (value.GetType().Name == expectedType)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And then instantiate it in code like this (or however you want):
List<object> instruments = new List<object>
{
new Guitar(),
new Guitar(),
new Violin(),
new Violin(),
new Drum()
};
instrumentsListBox.ItemsSource = instruments;
Now, I personally think (see here) that Silverlight data-binding should be strongly typed. But I seem to be in the minority there. And as long as it's not, there's no problem with taking advantage of it like this.

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