Avalonia icon indicating copy to clipboard operation
Avalonia copied to clipboard

System.StackOverflowException when using OneWayToSource binding

Open LtDetFrankDrebin opened this issue 4 years ago • 12 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 );.

LtDetFrankDrebin avatar Aug 04 '20 23:08 LtDetFrankDrebin

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

LtDetFrankDrebin avatar Aug 04 '20 23:08 LtDetFrankDrebin

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

grokys avatar Aug 06 '20 09:08 grokys

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.

LtDetFrankDrebin avatar Aug 07 '20 03:08 LtDetFrankDrebin

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;
		}
	}
}

LtDetFrankDrebin avatar Aug 08 '20 04:08 LtDetFrankDrebin

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 );

CollinAlpert avatar Mar 07 '22 17:03 CollinAlpert

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

d3jv avatar May 15 '23 21:05 d3jv

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

laolarou726 avatar Jun 03 '23 01:06 laolarou726

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

timunie avatar Dec 25 '23 17:12 timunie

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

Was not fixed in 11.0.7

MakesYT avatar Feb 03 '24 11:02 MakesYT

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

timunie avatar Feb 05 '24 08:02 timunie

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

MakesYT avatar Feb 05 '24 09:02 MakesYT

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.

rabbitism avatar Apr 13 '24 08:04 rabbitism

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

grokys avatar Jul 03 '24 19:07 grokys