Two-way databinding for a dictionary in UWP/XAML - c#

I currently have an application where I'm parsing a YAML dictionary. I have a model named Line that looks like this -
public class Line
{
private ObservableDictionary<string, string> language = new ObservableDictionary<string, string>();
public Line()
{
Language = new Dictionary<string, string>();
}
// used with the YAML parser
[YamlMember(Alias = "language")]
public Dictionary<string, string> Language { get; set; }
}
You've probably noticed that I'm using an ObservableDictionary, which is not a standard type. I took the code from this other StackOverflow answer. As I understand it, it's just setting up the necessary INotifyPropertyChanged interface.
Anyway, for my Line, I have a ListView populated with a dictionary of translations represented as the language abbreviation and a textbox. To better exemplify what I'm talking about, here is a graphic.
In my App.xaml, I have a DataTemplate for my ListView defined - together, they look like this:
<ListView
ItemTemplate="{StaticResource LinesTemplateItem}"
ItemsSource="{Binding Value.Language}"
SelectionMode="None">
</ListView>
...
<DataTemplate x:Key="LinesTemplateItem">
<StackPanel Background="Transparent">
<TextBlock Text="{Binding Key}" />
<TextBox Text="{Binding Value, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
Everything seems like it should work fine. My data shows properly. However, when I change a value, it does not update the underlying source with the error:
Error: Cannot save value from target back to source.
BindingExpression: Path='Value' DataItem='Windows.Foundation.Collections.IKeyValuePair`2<String,String>';
target element is 'Windows.UI.Xaml.Controls.TextBox' (Name='null');
target property is 'Text' (type 'String').
From the error, I would guess that for some reason the databinding is targeting the entire UI control instead of the text inside of it. I've been searching for a while, but I can't seem to figure out how to fix this error. Any help is appreciated. Thanks.

The problem is that the property IKeyValuePair.Value is read-only so you cannot modify it. My proposition is to change you data model a little bit i.e. firstly create an additional class to store translations.
public class Translation
{
public string Expression { get; set; }
}
Now you should also change a definition of your dictionary e.g.:
public Dictionary<string, Translation> Language { get; set; }
Binding should be also updated accordingly:
<TextBox Text="{Binding Value.Expression, Mode=TwoWay}" />
Thanks to that if you change a value, the data binding will update Expression property which is not read-only.
I didn't test this code but I did similar things in the past so something like that should work.

Since you're binding to Dictionary<string,string>, each item is bound to KeyValuePair<string,string>, which is of value type - it's fields cannot be changed (while unboxed). You should bind to the pair itself, rather than its parts and use value converter to produce a pair with changed value.

Related

How can I display the default enum value in ItemDisplayBinding in the Picker of Xamarin.Forms

View:
<Picker ItemsSource="{Binding ECCLevels}"
ItemDisplayBinding="{Binding QRCodeGenerator.ECCLevel}"
HeightRequest="44"/>
ViewModel:
public ObservableCollection<QRCodeGenerator.ECCLevel> ECCLevels { get; set; } = new ObservableCollection<QRCodeGenerator.ECCLevel>();
ECCLevels = new ObservableCollection<QRCodeGenerator.ECCLevel>(Enum.GetValues(typeof(QRCodeGenerator.ECCLevel)).OfType<QRCodeGenerator.ECCLevel>().ToList());
I simplified the code above. The ObservableCollection gets filled with data after the second line of code. But the problem is I don't know what to put in the ItemDisplayBinding property of the picker since there the Enum gets converted into a list directly without going through a model.
You can just use ItemDisplayBinding="{Binding .}", the dot notation means that you refer to this basically. So, this way you simply reference the object itself and not any other property.
In the comments you asked me how I know, that is actually I great question. I think I picked it up as early as WPF. But it's hard to dig up from the documentation, I found a small notice of it here: https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.binding.path?redirectedfrom=MSDN&view=netframework-4.7.2#System_Windows_Data_Binding_Path
Optionally, a period (.) path can be used to bind to the current source. For example, Text="{Binding}" is equivalent to Text="{Binding Path=.}".

XAML binding to a codebehind class property collection is blank (WPF)

My codebehind defines a simple class with properties and a constructor as so:
public class Question
{
public string[] Answers
{
get; set;
}
public int CorrectAnswerIndex
{
get; set;
}
public Question(string[] answers, int correctIndex)
{
this.Answers = answers;
this.CorrectAnswerIndex = correctIndex;
}
}
There then exists a public object of that type that gets initialised in the window's constructor like so:
CurrentQuestion = new Question(
new string[] { "First", "Second", "Third", "Fourth" }, 2
);
I then have the following XAML in an attempt to print out all of the possible answers from said question.
<Grid Margin="150,150,150,150" DataContext="local:CurrentQuestion">
<ListBox ItemsSource="{Binding Answers}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
The local namespace is defined previously as the CLR namespace.
However, my list emerges entirely empty. There are no binding errors at runtime.
What's going on here? It seems a simple example that just won't run. I feel I've missed something "obvious."
This will look at ListBox.DataContext for a property named Answers, and try to use that for the ItemsSource.
<ListBox ItemsSource="{Binding Answers}">
ListBox.DataContext will be inherited from the parent Grid. Unfortunately, the grid's DataContext is a string, and strings don't have a property called Answers. So the Binding can't do anything and gives you null.
<Grid Margin="150,150,150,150" DataContext="local:CurrentQuestion">
<ListBox ItemsSource="{Binding Answers}">
XAML implicit conversions are a Do-What-I-Mean thing, thus a source of much confusion. There are times when you can put local:CurrentQuestion in an attribute value and have it be taken as a data type -- but this is not one of those times. And a data type isn't what you meant to provide anyway. You wanted a property by that name. But local: is a namespace, a literal CLR namespace like System.Windows.Controls, not a reference to an object.
Here's how the XAML in a UserControl can bind to a property of the UserControl. If it's a Window, change UserControl to Window.
<Grid Margin="150,150,150,150">
<ListBox
ItemsSource="{Binding CurrentQuestion.Answers, RelativeSource={RelativeSource AncestorType=UserControl}}">
I'm just guessing that CurrentQuestion is a property of the UserControl. Let me know if it's somewhere else.
You're also probably going to run into problems when you update CurrentQuestion, unless it's a dependency property. If it's a plain old CLR property like this, the UI won't be notified when its value changes:
public Question CurrentQuestion { get; set; }

TwoWay Binding Not Updating Target - Metro App

I'm building a Metro App using VS 2012 and the Windows 8 SDK. In the app, I have this class (with a corresponding struct)
// Parameter data structure for tools
public struct ToolParameter
{
public string Title { get; set; }
public Object Value { get; set; }
public string Description { get; set; }
}
// Tool that will be used to execute something on phone
public class Tool
{
public string Title{ get; set; }
public ObservableCollection<ToolParameter> Parameters { get; set; }
public string Description { get; set; }
}
On a certain page in the app, I bind an instance of the class to the dataContext of the page
this.DataContext = currentTool;
On the page, I display various information about the app, including the parameters, which I want to make editable on the page. Because of this, I'm using a TextBox to display the parameters so that it can be edited, and binding it to the "Value" member of the ToolParameter struct.
<TextBox x:Name="ParameterValue" FontSize="15" Text="{Binding Value, Mode=TwoWay}" TextWrapping="Wrap"/>
Unfortunately, when a TextBox is bound to a value, it doesn't update until it no longer has a focus, so I added a button that the user can click that will update the parameters (and change focus from the TextBox). Unfortunately, upon clicking of the button, though the focus changes, the values of the parameter in the currentTool variable is never changed. Is there something about data binding that I am missing? Might it be that the parent of the TextBox named ParameterValue (the parameters are all part of a ListView) has to be two way as well?
From what I can see, youre TextBox is binding to Value which is a property of the ToolParameter class. The DataContext for the page is of type Tool. Tool contains Parameters which is a collection of ToolParameter objects. So, the TextBox needs to be within an ItemsCollection that has the ItemsSource set to bind to the Parameters property.
Example:
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding Description}"/>
<!-- showing a ListBox, but can be any ItemsControl -->
<ListBox ItemsSource="{Binding Parameters}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Also make sure that your classes Tool and ToolParameter implement INotifyPropertyChanged and that the setter for your properties fire the PropertyChanged event
UPDATE: Adding info that was too large for a comment
This should help understand Source/Target in bindings. For your TextBox, the source of the binding is the Value property and the Target is the TextProperty of the TextBox. When the source updates, the Text will update within the TextBox. If you the TextProperty of the TextBox changes, then it will update the Value property of your object (provided mode is set to TwoWay). You're tool however will NOT update and neither will the Parameters property of the Tool class. If you wish to update the tool object when a property of a ToolParameter updates, then you will need to subscribe to the PropertyChanged event of each ToolParameter object that gets added to the Parameters collection.
Welcome to StackOverflow!
In the binding, you can specify the UpdateSourceTrigger to 'PropertyChanged':
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
The default value, which you are experiencing, is 'LostFocus'.

viewmodel for gridview with column headers c#

I need a solution for this.
I need to create a view model for grid view. This viewmodel should be a strong typed one. ex.
List<Person> lstPersons=new List<Person>();
something like this. also, with this I should be able to have custom column header names. I can go with data annotation with enabling AutoGenerateColumns="True"
like,
class Person
{
[DisplayName("Person Name")]
public string name { get; set; }
}
something like this. But I have 2 issues with this.
I donno how to change this display name at run time.
Im using telerik RADGridView. with that when I'm using AutoGenerateColumns="True" and ShowColumnFooters="True", the whole UI get stucked. I think this s an error with telerik controls. So I have to define all columns in XAML and add binding path for each as well.
Anyway, This is possible with DataTable I think. but I feel data tables are very oldie struct and heavy object.
How to create a Viewmodel to achieve this? Any suggestions ?
Feel free to ask any question. im not sure above description is clear to everyone.
You can continue to use the DisplayName attribute on your model, but as you pointed out, cannot change it at run-time. To do so, implement a dictionary in your ViewModel that populates
public PeopleGridViewModel
{
public ObservableCollection<Person> People;
public Dictionary<string, string> PersonColumnHeaders;
public PeopleGridViewModel()
{
// 1. write C# here to snag the property names from the typeof(Person)
// 2. get the DisplayName attribute value for that property too.
// 3. add the propertyName.ToString() and the DisplayName string to the dictionary as a key/value pair.
// the result is you have a collection of column headers that start with the defaults from the propertys on the object... but they can be manipulated at run-time, and you don't havem them 100% hard typed like in adcool's example.
}
}
I think the show column footers deal is a Telerik issue as you suggested.
Try this discussion about data binding the column header.
I assume that the columns you want ot display is Fixed.... so your ViewModel Will be like
class MyViewModel
{
//Implement Properties nad proper binding in Xaml and INotifyPropertyChanged for every property that you need on View Level
ObservableCollection<Persons> persons;
string columnheader1;
string columnheader2;
string columnheader3;
}
XAML
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn DataMemberBinding="{Binding UserName}" Width="200">
<telerik:GridViewDataColumn.Header>
<TextBlock Text="{Binding Columnheader1}"></TextBlock>
</telerik:GridViewDataColumn.Header>
</telerik:GridViewDataColumn>
<telerik:GridViewDataColumn Header="{Binding Columnheader2}" DataMemberBinding="{Binding IsOnline}" MinWidth="200" Width="*"/>
<telerik:GridViewDataColumn Header="{Binding Columnheader3}" DataMemberBinding="{Binding LastActivityDate}" MinWidth="200"/>
</telerik:RadGridView.Columns>
This might do the trick..... :)

Editable WPF ListBox

I have a ObservableCollection that's bound to a ListBox in WPF. I want the ListBox to be editable, and for the editing changes to be saved to the collection. Since WPF doesnt provide an editable listbox, I've tried creating my own by changing the ListBox.ItemTemplate.
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="EditableText" Text="{TemplateBinding Content}"/>
</DataTemplate>
</ListBox.ItemTemplate>
Changing the ItemTemplate gives me editable boxes, but any changes to the textboxes dont get saved to the ObservableCollection. Is there a way to have an editable ListBox with two way binding?
You cannot do it this way.
To achieve that kind of trick, you would need your items to be "holder classes" that expose a property you can bind your textbox to.
To understand it, imagine the following pseudo sequence of calls:
class ListBox
{
Bind(Items)
{
foreach(var item in Items)
{
DataTemplate Template = LoadTemplateForItem(item.GetType()); // this is where your template get loaded
Template.Bind(item); //this is where your template gets bound
}
}
}
Your template (the DataTemplate with the listbox) is loaded and the item (which I assume is a string in your case) gets passed in.
At this point, it only knows the string, and cannot influence anything upwards. A two-way binding cannot influence the collection because the template does not know in which context it is being used, so it cannot reach back to the original collection and modify its contents.
For that matter, this is the same thing for the TextBox. If it is not given a conainer and a property name, it has nowhere to "store back" the changes.
This basically the same as passing a string into a function call. The function cannot change which string was passed in (ignoring tricks such as by-reference argument passing).
To get back to your case, you need to build a collection of objects which expose a property containing the value that needs to be edited:
public class MyDataItem
{
string Data { get; set;}
}
Then you can bind your ListBox to a collection of those items and modifiy your datatemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="EditableText" Text="{Binding Data, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
Bind to a model property -- i.e. a property of the data object -- rather than to a view property such as Content. For example:
// model class
public class Widget : INotifyPropertyChanged
{
public string Description { ... }
}
<!-- view -->
<DataTemplate>
<TextBox Text="{Binding Description}" />
</DataTemplate>
Note this will not work if your ItemsSource is ObservableCollection (because there's no property to bind to).

Categories