AvaloniaUI/Avalonia

System.StackOverflowException when using OneWayToSource binding

LtDetFrankDrebin opened this issue · 13 comments

When I use OneWayToSource binding from a control to ViewModel I get wrong behavior:

  1. StackOverflowException happens.
  2. In spite of OneWayToSource binding control tries to read from Property in ViewModel.

Example 1:
View:

<Window xmlns="https://github.com/avaloniaui"
				xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
				xmlns:vm="clr-namespace:Tests.ViewModels;assembly=Tests"
				xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
				xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
				mc:Ignorable="d"
				d:DesignWidth="800" d:DesignHeight="450"
				x:Class="Tests.Views.MainWindowView"
				Icon="/Assets/avalonia-logo.ico"
				Title="Tests">

	<Design.DataContext>
		<vm:MainWindowViewModel/>
	</Design.DataContext>

	<ItemsControl Items="{Binding Items}">
		<ItemsControl.ItemTemplate>
			<DataTemplate>
				<StackPanel>
					<TextBlock Bounds="{Binding Bounds, Mode=OneWayToSource}" />
				</StackPanel>
			</DataTemplate>
		</ItemsControl.ItemTemplate>
	</ItemsControl>

</Window>

ViewModel:

using Avalonia;
using ReactiveUI;
using System.Collections.Generic;

namespace Tests.ViewModels
{
	public class MainWindowViewModel : ReactiveObject
	{
		public class Item : ReactiveObject
		{
			Rect _bounds;
			public Rect Bounds
			{
				get => _bounds;
				set => this.RaiseAndSetIfChanged( ref _bounds, value );
			}
		}

		List<Item> _items = new List<Item> { new Item(), new Item(), };
		public List<Item> Items
		{
			get => _items;
			set => this.RaiseAndSetIfChanged( ref _items, value );
		}
	}
}

Result:
After executing I get infinity loop in set => this.RaiseAndSetIfChanged( ref _bounds, value );.

Example 2:
View:

<Window xmlns="https://github.com/avaloniaui"
				xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
				xmlns:vm="clr-namespace:Tests.ViewModels;assembly=Tests"
				xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
				xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
				mc:Ignorable="d"
				d:DesignWidth="800" d:DesignHeight="450"
				x:Class="Tests.Views.MainWindowView"
				Icon="/Assets/avalonia-logo.ico"
				Title="Tests">

	<Design.DataContext>
		<vm:MainWindowViewModel/>
	</Design.DataContext>

	<Grid RowDefinitions="* *" ColumnDefinitions="* *" ShowGridLines="True">
		<TextBlock Grid.Row="0" Grid.Column="0" Width="100" Height="100" Background="Red"
							 IsPointerOver="{Binding IsActive, Mode=OneWayToSource}" Text="{Binding IsActive, Mode=OneWay}" />
		
		<TextBlock Grid.Row="1" Grid.Column="1" Width="100" Height="100" Background="Blue"
							 IsPointerOver="{Binding IsActive, Mode=OneWayToSource}" Text="{Binding IsActive, Mode=OneWay}" />
	</Grid>
</Window>

ViewModel:

using ReactiveUI;

namespace Tests.ViewModels
{
	public class MainWindowViewModel : ReactiveObject
	{
		bool _isActive;
		public bool IsActive
		{
			get => _isActive;
			set => this.RaiseAndSetIfChanged( ref _isActive, value );
		}
	}
}

Result:
After executing and moving mouse over red or blue square I get infinity loop in set => this.RaiseAndSetIfChanged( ref _isActive, value );.

I'm using <PackageReference Include="Avalonia" Version="0.9.11" />.

@LtDetFrankDrebin could you fix your markdown? I'm having trouble reading it.

Thanks to the one who fixed markdown for me. I used "Insert code" button <> from tool panel and it added only single ` quotes, but with them "<Window" tags are invisible. Now I know that triple ``` quotes needed for code.

As a workaround I use MultiValueConverter now:
View:

<Window xmlns="https://github.com/avaloniaui"
				xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
				xmlns:vm="clr-namespace:Tests.ViewModels;assembly=Tests"
				xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
				xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
				mc:Ignorable="d"
				d:DesignWidth="800" d:DesignHeight="450"
				x:Class="Tests.Views.MainWindowView"
				Icon="/Assets/avalonia-logo.ico"
				Title="Tests">

	<Design.DataContext>
		<vm:MainWindowViewModel/>
	</Design.DataContext>

	<Window.Resources>
		<vm:MultiValueConverter x:Key="MultiValueConverter" />
	</Window.Resources>

	<Grid RowDefinitions="* *" ColumnDefinitions="* *" ShowGridLines="True">
		<TextBlock Grid.Row="0" Grid.Column="0" Width="100" Height="100" Background="Red" Text="{Binding IsActive, Mode=OneWay}">
			<TextBlock.Tag>
				<MultiBinding Converter="{StaticResource MultiValueConverter}">
					<Binding RelativeSource="{RelativeSource Self}" Path="DataContext" />
					<Binding RelativeSource="{RelativeSource Self}" Path="IsPointerOver" />
				</MultiBinding>
			</TextBlock.Tag>
		</TextBlock>
		
		<TextBlock Grid.Row="1" Grid.Column="1" Width="100" Height="100" Background="Blue" Text="{Binding IsActive, Mode=OneWay}">
			<TextBlock.Tag>
				<MultiBinding Converter="{StaticResource MultiValueConverter}">
					<Binding RelativeSource="{RelativeSource Self}" Path="DataContext" />
					<Binding RelativeSource="{RelativeSource Self}" Path="IsPointerOver" />
				</MultiBinding>
			</TextBlock.Tag>
		</TextBlock>
	</Grid>
</Window>

ViewModel:

using Avalonia;
using Avalonia.Data.Converters;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Globalization;

namespace Tests.ViewModels
{
	public class MainWindowViewModel : ReactiveObject
	{
		bool _isActive;
		public bool IsActive
		{
			get => _isActive;
			set => this.RaiseAndSetIfChanged( ref _isActive, value );
		}
	}

	public class MultiValueConverter : IMultiValueConverter
	{
		public object Convert( IList<object> values, Type targetType, object parameter, CultureInfo culture )
		{
			if ( !(values[ 0 ] is MainWindowViewModel vm) )
				return AvaloniaProperty.UnsetValue;
			if ( !(values[ 1 ] is bool isPointerOver) )
				return AvaloniaProperty.UnsetValue;

			return vm.IsActive = isPointerOver;
		}
	}
}

I am running into this issue as well. Having two OneWayToSource binding binding to the same object result in an infinite loop when calling this.RaiseAndSetIfChanged( ref _value, value );

d3jv commented

This also happens when binding DatePicker. Seems like the "Date" and "SelectedDate" are both trying to update.

@grokys Any updates on this issue? I have to remove most of the OneWayToSourceBinding to avoid the StackOverFlow error.

Anyone fancy to test if #13970 would solve this issue?

Anyone fancy to test if #13970 would solve this issue?

Was not fixed in 11.0.7

Was not fixed in 11.0.7

That's expected as the PR was not released yet. It's only testable in nightly builds. https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed

Was not fixed in 11.0.7

That's expected as the PR was not released yet. It's only testable in nightly builds. https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed

I'm sorry that I saw that the PR had been merged and subconsciously thought it was already in the release version
But in 11.1.999-cibuild0043857-beta also not fix

I find a case that constantly triggering this issue. Binding a nullable control property to a non-nullable vm property with OneWayToSource will trigger this StackOverflow.

nvm, tested new nightly, the behavior changed.

Just tried this on latest master and it seems to be fixed. I think it will have been fixed by #13970.