The problem with your binding is that you are trying to bind to an object. This is perfectly fine in a OneWay
/OneTime
scenario. But not when using binding TwoWay
. You can change the value of a property e.g. in your view model, but you can't change the object instance itself. In your specific case, the binding would have to send the new long
input to the view model's value collection and replace the old value. Of course this will never happen as Binding
is not designed to work this way.
The technical reason is that changing the instance would mean to change the Binding.Source
. Once the binding is active (controlled by a BindingExpression
) it becomes immutable. Changing the source is not allowed. That's also the reason why {Binding Source={DynamicResource ...}}
won't work. The BindingSource
can only be static (or StaticResource
- not changing resource).
You usually bind to properties. In a TwoWay
binding scenario Binding
can simply update the property's value. So the solution to your problem is to wrap the long
values into a class and bind the TextBox
to a property of this class to retrieve/modify the actual value.
In this context your code looks too complicated.
Your object structure is too complex or unnatural.
You don't need to apply the DataTemplate
to a ContentControl
(in XAML).
And of course as this is a UWP application, use x:Bind
where possible as it will improve performance. The converter is redundant as Binding
and x:Bind
allow a nested PropertyPath
e.g.
<ListView ItemsSource="{Binding CurrentParameterValue.ListParameterValues}">
ItemsControl.ItemsSource
doesn't need a TwoWay
binding. The ItemsControl
will never update/replace the source collection. If you don plan to replace the source collection in the view model (e.g., AtlasMethodParameterList = new ObservableCollection<>()
), then you can even set the binding mode to OneTime
(which would be the default for x:Bind
).
I recommend to use OneTime
and if you need to replace the collection, rather call Clear()
on the collection and add the new items. This will improve the performance.
Never use async void
in a method signature except for event handlers.
Always use async Task
, when the return type is void
or when returning a value async Task<TResult>
. Otherwise you will experience unexpected side effects, especially when encountering exceptions:
// An async void method must return Task
private async Task GetParameterList(string obj)
Also async
methods should always be awaited. This means the method calling and awaiting an async
method must itself return Task
or Task<T>
to be awaitable. A method returning type void
cannot be awaited.
All DependencyProperty
of every control, have their Binding.UpdateSourceTrigger
set to UpdateSourceTrigger.PropertyChanged
by default.
Exceptions are properties that are likely to raise too much consecutive property changes like a TextBox
would do on each input/key press. TextBox.Text
has the default set to UpdateSourceTrigger.LostFocus
.
You should remove all redundant UpdateSourceTrigger.PropertyChanged
from the bindings to improve readability.
Consider to use out
instead of ref
if you don't intend to read the variable. If you only set the value prefer to use out
to hint your intent to any reader. Use in
if don't intent to modify the reference (read-only reference).
Your Set
method should look something like this:
protected virtual void Set<TValue>(out TValue valueTarget, TValue value, [CallerMemberName] string propertyName = null)
{
if (value != valueTarget)
{
valueTarget = value;
OnPropertyChanged(propertyName);
}
}
I refactored your complete code trying to improve it:
Parameter.cs
// The type that wraps the actual parameter value.
// Consider to use dedicated types e.g., LongParameter instead, to allow a strongly typed Value property instead of a basic property of type object.
// This prevents implicit boxing/unboxing in order to convert from object/reference type to primitive/value type and vice versa. This will improve performance.
// (Only needed because we are dealing with primitive/value types like long, double, etc)
// You would then have to define a DataTemplate for each type. Don't forget to set x:DataType on each DataTemplate.
public class Parameter : BindableBase
{
protected Parameter(object value)
{
this.Value = value;
}
private object value;
public object Value
{
get => this.value;
set => Set(out this.value, value);
}
}
VmServiceModel.cs
public class VmServiceModel : BindableBase
{
public VmServiceModel()
{
this.Parameters = new List<Parameter>();
}
private List<Parameter> _parameters;
public List<Parameter> Parameters
{
get => this._parameters;
set => Set(out this._parameters, value);
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.AtlasMethodParameterList = new ObservableCollection<VmServiceModel>();
}
private ObservableCollection<VmServiceModel> _atlasMethodParameterList;
public ObservableCollection<VmServiceModel> AtlasMethodParameterList
{
get => _atlasMethodParameterList;
set => Set(out _atlasMethodParameterList, value);
}
private async Task GetParameterList(string obj)
{
foreach (var item in this.ParametersCollection)
{
var vmServiceModel = new VmServiceModel();
vmServiceModel.Parameters
.AddRange(item.Value.Cast<long>().Select(innerItem => new Parameter(innerItem)));
this.AtlasMethodParameterList.Add(vmServiceModel);
}
}
}
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
}
MainPage.xaml
<Page>
<Page.Resources>
<DataTemplate x:Key="ListIntTemplate" x:DataType="local:VmServiceModel">
<ListView ItemsSource="{x:Bind Parameters}"
HorizontalAlignment="Center"
SelectionMode="None" Background="Transparent">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Parameter">
<TextBox Text="{Binding Value Mode=TwoWay}" Height="36" Width="65"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</Page.Resources>
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.AtlasMethodParameterList}"
ItemTemplate="{StaticResource ListIntTemplate}">
</ListView>
</Grid>
</Page>