WPF binding tricky issue - wpf

I need some assistance to implement some data binding. My viewmodel exposes the following properties:
public List<string> ChosenFeatures {get;set;}
public Dictionary<string, double> AllFeatureCosts {get;set;}
"ChosenFeatures" will contain a subset of dictionary keys present in "AllFeatureCosts".
In the view I would like to render a series of TextBlocks, one for each item in "ChosenFeatures". Here's the tricky part:- the Text property of each TextBlock need to be bound to a value in the "AllFeatureCosts" dictionary, using the string in "ChosenFeatures" as the key to that dictionary item.
I would be grateful for any pointers on how to write the XAML to accomplish this.

Provide a ViewModel for the data, thats the reason to use MVVM in the first place.
class FeatureViewModel
{
public FeatureViewModel(MyViewModel aViewModel, string aKey)
{
mParent = aViewModel;
mKey = aKey
}
public string Value
{
get{return mParent.AllFeatureCosts[mKey];}
}
}
add a collection for your viewmodels to your main viewmodel
public ObservableCollection<FeatureViewModel> Features{ get; set; }
and initialize it somewhere
foreach(var feature in ChosenFeatures)
{
Features.Add(new VisualFeature(this, feature) );
}
from here you can also if necessary and if you have INotifyPropertyChanged properly implemented, raise any changes on the FeatureViewModels. You of course need to keep these collections in sync, which might be a bit of work.
Of course, your DataTemplate needs some adjustments aswell
<DataTemplate DataType="{x:Type FeatureViewModel}">
<TextBlock Text="{Binding Value}"/>
</DataTemplate>

First of all, I suppose you should use #Jay's approach and make ChosenFeatures Dictionary also.
However you can use Converter instead and pass your dictionary like a parameter while binding:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = (string)value;
var dictionary = (Dictionary<string, double>)parameter;
if (dictionary.ContainsKey(key))
return dictionary[key];
else
return null;
}

Instead of binding "ChosenFeatures" bind "AllFeatureCosts". We know that it will display complete list and we can then write a simple Multibinding visibility converter to display the items which are selected (in ChosenFeatures).
Note:
Depending on the size of the dictionary it may affect application performance...

Related

Binding to an expression

I have an onscreen numeric keypad to type a PIN. What I want to do is disable the buttons when four digits of PIN are entered. I can certainly do this with code pretty easily, but it seems to me to be the sort of thing that should be done with binding.
Something like:
<Button Style="Whatever" IsEnabled="{Binding ElementName=PinBox ???}"/>
It seems there isn't a way to do that (which to be honest seems rather primitive to me.) So I considered the alternative, which is a plain property on the underlying Window class. But I'm not sure how to bind to it. Do I need to specify the class itself as its own data context, or do I need to extract the PIN string into a View Model?
And subsequently, how do I get the plain property to update the GUI?
I suppose I could defined a view model class and have a dependency property called "ButtonsEnabled" but it seems kind of heavyweight for such a simple problem.
Let me know if I am missing something.
You can write a converter which return boolean depending on digits in TextBox
The XAML fo r button would be
<Button Content="Test" IsEnabled="{Binding ElementName=PinBox,Path=Text,Converter={StaticResource DigitsToBoolConverter}}" Grid.Row="1" Height="20" Width="100"></Button>
where PinBox is the textbox name used to enter pin.
The Converter function is
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString().Length >= 4;
}
Another way using commands:
XAML:
<Button Content="2" Style="Whatever" Command={Binding MyCommand} CommandParamater="2"/>
ViewModel:
public ICommand MyCommand { get; private set; }
public string PinNumber { get; private set; }
public void Init()
{
MyCommand = new RelayCommand(
param => AddPinNumberDigit(param),
param => CanAddPin);
}
private void AddPinNumberDigit(string digit)
{
PinNumber += digit;
}
public bool CanAddPin {
get
{
return PinNumber.Length < 3;
}
}
Nope, your not missing anything, WPF out of the box bindings do not support expressions.
There has been some people implementing their own classes that add this type of functionality:
http://www.11011.net/wpf-binding-expressions
But really, this is what the ViewModel pattern is for. Use it, it's not heavyweight.
Create a converter that will return true or false based on PinBox.Text.Length.
Your xaml would then become:
<Button Style="Whatever" IsEnabled={Binding ElementName=PinBox, Converter={StaticResource yourConverter}}/>

How to resolve/control property name collision in XAML binding?

Interview Question
Phrased as:
If you have a property name collision, how would you specify the exact property to bind to in a Binding path expression (in XAML)?
I never faced this (property name collision) problem in any binding so far. With some reading I realized that this is possible in case I am binding to a overridden property because then I have two instances of this property (virtual in base, and overriden in derived) as far as resolution using Reflection is concerned. Which is what used by XAML.
Could there be any other case where XAML might face a property name collision?
Is there some support in API to handle/control that? (Instead of of course avoiding a collision)
Thanks for your interest.
Sounds like a complete nonsense to me. Unless they wanted to talk about bindings, using 'disjointed' sources like PriorityBinding and MultiBinding.
Frankly speaking I don't think overwritten properties can be involved into the matter as this is so much out of scope, you could equaly point out explicit interface implementations and many other things, which are clearly outside of WPF domain.
The best way I can think would be to use a ValueConverter. I don't think this really answers the question though since they're asking in a binding path expression, which I haven't seen to be possible. I'm not particularly fond of doing it this way because it feels like a hack, but it works at least for one way binding. Here's an example of how you might do it:
XAML:
<StackPanel Name="stack">
<StackPanel.Resources>
<loc:OverriddenMyPropertyConverter x:Key="BaseMyProperty"/>
</StackPanel.Resources>
<TextBox Text="{Binding Path=MyProperty}"/>
<TextBox Text="{Binding Mode=OneWay, Converter={StaticResource BaseMyProperty}}"/>
</StackPanel>
The DataContext of the StackPanel is an instance of MyClass. The first TextBox is bound to the MyClass.MyProperty property, and the second TextBox will be bound to the MyBaseClass.MyProperty property. Two way binding would be a bit more complex since the object actually being bound to the second TextBox is the MyClass object and not the MyProperty object.
Code:
class MyClass : MyBaseClass
{
string myProperty = "overridden";
public new string MyProperty
{
get { return myProperty; }
set { myProperty = value; }
}
}
class MyBaseClass
{
string baseProperty = "base";
public string MyProperty
{
get { return baseProperty; }
set { baseProperty = value; }
}
}
class OverriddenMyPropertyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value as MyBaseClass).MyProperty;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Databinding with kind of join over two lists

I am familiar with the basics of databinding in wpf. However I now have a problem which I wonder how to solve.
Imagine following use case:
I have a global ObservableCollection called "AItems" of Type A.
I have some Objects of Type B and each has a ObservableCollection "BItems" of type A.
The BItems Collections can contain Objects of the global AItems Collection.
I want to visualize this by a ListView.
Each line should contain an A-Object and a checkbox.
I want the ListView to show all elements of the AItems-Collection. Items which are assigned to the B-Object should be marked with a checked checkbox. All other checkboxes should be unchecked.
My questions are now:
How should I set the datacontext?
How can I make that checking a checkbox inserts its item to the BItems-Collection and unchecking removes it?
I hope anyone can understand my problem.
Thanks for replies.
I'm not clear on the latter part of your question. Partly it's because your naming convention is confusing; I'd expect a collection named BItems to contain objects of type B, not A.
So I'm going to change your nomenclature a bit so that I don't get confused. Instead of A, I'll call the first class User, and instead of B, I'll call the second class Group. A Group contains a collection of User objects, named Users. The global collections look like this:
List<User> Users;
List<Group> Groups;
It's easy to determine if a given User u is in any group:
return Groups.Where(g => g.Users.Contains(u)).Any();
Easy, but computationally expensive if you have many groups and they contain many users. We'll get back to that in a second.
Right away, I see that one of your questions has got a problem:
How can I make that checking a checkbox inserts its item to the BItems-Collection and unchecking removes it?
What should happen if I check an unchecked user? Which group (or groups, since more than one group can contain a user) should it be added to?
Since you say that you want checked items to be "assigned to the B-Object", I'm going to assume that the UI is only looking at one group at a time - we'll call it the SelectedGroup. This is good, because g.Users.Contains(u) is much less expensive than the query I showed above.
If this is so, what you need to do is wrap your User in a class that exposes an IsChecked property. I'd call this class UserViewModel, since that's what it is. The class needs three properties (at a minimum):
public User User { get; set; }
public Group SelectedGroup { get; set; }
public bool IsChecked
{
get { return SelectedGroup.Users.Contains(this.User); }
set
{
if (value != IsChecked)
{
if (IsChecked)
{
SelectedGroup.Users.Remove(this.User);
}
else
{
SelectedGroup.Users.Add(this.User);
}
}
}
}
Your ListView is bound to an ObservableCollection<UserViewModel> named, say, UserViewModels. Whenever SelectedGroup is set, you need to rebuild this collection:
UserViewModels = new ObservableCollection<UserViewModel>(
Users.Select(u => new UserViewModel { User=u, SelectedGroup=SelectedGroup }));
You could avoid rebuilding the collection by implementing INotifyPropertyChanged in the UserViewModel class, and having it raise PropertyChanged for the IsChecked property whenever SelectedGroup changes.
Also, it would probably be responsible to include null-reference checking in the IsChecked property, so that the program doesn't throw an exception if SelectedGroup or SelectedGroup.Users is null.
You can bind a list box to Aitems and using a converter to set the isChecked property
<ListBox ItemsSource="{Binding AItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" IsChecked="{Binding Mode=OneTime, Converter={StaticResource BItemCheckConverter}}"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public class BItemCheckConverter : IValueConverter
{
public List<Aitems> BItems { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (BItems.Contains((value as Aitems)) return true;
else return false
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I don't know how's your object model, so take the above code as is.
HTH

DataGrid: dynamic DataTemplate for dynamic DataGridTemplateColumn

I want to show data in a datagrid where the data is a collection of
public class Thing
{
public string Foo { get; set; }
public string Bar { get; set; }
public List<Candidate> Candidates { get; set; }
}
public class Candidate
{
public string FirstName { get; set; }
public string LastName { get; set; }
...
}
where the number of candidates in Candidates list varies at runtime.
Desired grid layout looks like this
Foo | Bar | Candidate 1 | Candidate 2 | ... | Candidate N
I'd like to have a DataTemplate for each Candidate as I plan changing it during runtime - user can choose what info about candidate is displayed in different columns (candidate is just an example, I have different object). That means I also want to change the column templates in runtime although this can be achieved by one big template and collapsing its parts.
I know about two ways how to achieve my goals (both quite similar):
Use AutoGeneratingColumn event and create Candidates columns
Add Columns manually
In both cases I need to load the DataTemplate from string with XamlReader. Before that I have to edit the string to change the binding to wanted Candidate.
Is there a better way how to create a DataGrid with unknown number of DataGridTemplateColumn?
Note: This question is based on dynamic datatemplate with valueconverter
Edit: As I need to support both WPF and Silverlight, I've created my own DataGrid component which has DependencyProperty for bindig a collection of columns. When the collection changes, I update the columns.
For example we create 2 DataTemplates and a ContentControl:
<DataTemplate DataType="{x:Type viewModel:VariantA}"> <dataGrid...> </DataTemplate>
<DataTemplate DataType="{x:Type viewModel:VariantB}"> <dataGrid...> </DataTemplate>
<ContentControl Content="{Binding Path=GridModel}" />
Now if you set your GridModel Property (for example type object) to VariantA or VariantB, it will switch the DataTemplate.
VariantA & B example Implementation:
public class VariantA
{
public ObservableCollection<ViewModel1> DataList { get; set; }
}
public class VariantB
{
public ObservableCollection<ViewModel2> DataList { get; set; }
}
Hope this helps.
I don't know if this is a "better" way, since this remains pretty ugly, but I personnaly did like this:
make the template in xaml
use a multibind that takes the current binding + a binding to the column to get the "correct" dataContext (i.e.: the cell instead of the row)
use a converter on this binding to get the value of the property you like, an optionally add a parameter if you have many properties to retrieve.
e.g.: (sorry, I did not adapt my code to suit your project, but you should be able to do it yourself from there)
here is my dataTemplate:
<DataTemplate x:Key="TreeCellTemplate">
<Grid>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5,0,0,0">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource RowColumnToCellConverter}" ConverterParameter="Text">
<Binding />
<Binding RelativeSource="{RelativeSource AncestorType=DataGridCell}" Path="Column" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</DataTemplate>
and here is my converter:
public class RowColumnToCellConverter : MarkupExtension, IMultiValueConverter
{
public RowColumnToCellConverter() { }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
XwpfRow row = values[0] as XwpfRow;
XwpfTreeColumn column = values[1] as XwpfTreeColumn;
if (row == null || column == null) return DependencyProperty.UnsetValue;
TreeCell treeCell = (TreeCell)row[column.DataGrid.Columns.IndexOf(column)];
switch ((string)parameter)
{
case "Text": return treeCell.Text;
case "Expanded": return treeCell.Expanded;
case "ShowExpandSymbol": return treeCell.ShowExpandSymbol;
case "CurrentLevel": return new GridLength(treeCell.CurrentLevel * 14);
default:
throw new MissingMemberException("the property " + parameter.ToString() + " is not defined for the TreeCell object");
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new RowColumnToCellConverter();
}
}
this saves the MVVM model, and I prefer this way of doing things because I really dislike using xaml parsers to make "dynamic" datatemplates, but it's still an ugly Hack from my point of view.
I wish the guys at MS would give us a way to get cells instead of rows as dataContexts to be able to generate templated columns on the fly...
hope this helps
EDIT: In your case, the converter ought to be a lot simpler actually (you can return the cell's instance directly if I'm not mistaken, and you don't need any parameter), but I left the more complex version nonetheless, just in case somebody else has a similar issue
I've been looking at a similar problem and have only found a handful of useful patterns. The whole 'dynamic column' problem is an interesting one in silverlight.
Yesterday I found this page Silverlight DataGrid with Dynamic Columns on Travis Pettijohn's site during my searches.
Previously I'd been using the 'index converter' pattern outlined by Colin Eberhardt which works fantastically well... as long as you use DataGridTextColumn. Everything can be done in code behind, and I had no trouble applying styles at run time. However my requirement is now to apply some 'cell level' formatting - change the background for the cell, etc - which means a DataGridTemplateColumn is required.
The big problem with a DataGridTemplateColumn for me was that I can't set the binding in code. I know we can build it by parsing xaml, but like everyone else that seems like a massive hack and unmaintainable to the nth.
The pattern outlined by Travis (the first link above) is completely different. At 'run time' (i.e. page load time), create the columns you need in your grid. This means iterate through your collection, and add a column for each item with the appropriate header etc. Then implement a handler for the RowLoaded event, and when each row is loaded simply set the DataContext for each cell to the appropriate property / property index of the parent.
private void MyGrid_RowLoaded(object sender, EventArgs e)
{
var grid = sender as DataGrid;
var myItem = grid.SelectedItem as MyClass;
foreach (int i = 0; i < myItem.ColumnObjects.Count; i++)
{
var column = grid.Columns[i];
var cell = column.GetCellContent(e.Row)
cell.DataContext = myItem.ColumnObjects[i];
}
}
This has removed the need for me to use the index converter. You can probably use a Binding when setting the cell.DataContext but for me it's easier to have the template simply bind directly to the underlying object.
I now plan on having multiple templates (where each can bind to the same properties on my cell object) and switching between them at page load. Very tidy solution.

Pass view to viewmodel with datatemplate

I have a window named ParameterEditorView with a ParameterEditorViewModel as DataContext. In the ParameterEditorViewModel I have a list of ParameterViewModel. In the ParameterEditorView I have an ItemsControl whose ItemsSource is binded to the list of ParameterViewModel in the ParameterEditorViewModel. I need the ParameterViewModel to have a reference to the ParameterView (more on that later). In the Resources section of the ParameterEditorView I add the DataTemplate:
<DataTemplate DataType="{x:Type my:ParameterViewModel}" >
<my:ParameterView HorizontalAlignment="Left"/>
</DataTemplate>
So, how can I pass a reference of the ParameterView that is created to show the ParameterViewModel to it?
The reason I need the ParameterView in the ParameterViewModel is the following:
I have a TextBox whose Text property is binded to the PropertyModelView.Name property. But I want to display a default string when the Name is empty or Null. I've tried to set the property value to the default string I want when that happens but the TextBox.Text is not set in this scenario. I do something like this:
private string _name;
public string Name
{
get { return _name; }
set
{
if (value == null || value.Length == 0)
Name = _defaultName;
else
_name = value;
}
}
I've also tried to specifically set the TextBox.Text binding mode to TwoWay without success.
I think this is a defense mechanism to prevent an infinite loop from happening but I don't know for sure.
Any help on this front would also be highly appreciated.
Thanks,
José Tavares
{Binding } has a FallbackValue, btw.
Your question, it confuses me. I'd assume your PVM has a collection of PV's as a public property, which is bound within the UI. Also, I think you're mixing terms. Its Model-View-ViewModel where the ViewModel is the DataContext of the View, and the Model is exposed by the ViewModel via a public property. Sounds like if you're binding the window to a collection of ViewModels they are actually Models. It may seem pedantic, but getting your terms correct will help you research and ask questions.
Another solution would be to add a Converter to your Binding in combination with FallbackValue (I've had to do this, IIRC). That converter would be an IValueConverter that returns "DependencyProperty.UnsetValue" if the string is null or empty. I think this works sometimes because the TextBox will set the bound property to the empty string rather than null if the TB is empty. Here's a little sample to whet your whistle (not guaranteed to work; you need to debug this and tweak it):
public class ThisMightWorkConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
var temp = value as string;
if(string.IsNullOrWhiteSpace(temp))
return System.Windows.DependencyProperty.UnsetValue;
return temp;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return value; // you might need to change this
}
}

Resources