Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

View's DataContext is getting set to the parent's view DataContext at initialization, triggering the error "Unable to cast object of type" #17649

Closed
ZecosMAX opened this issue Nov 29, 2024 · 9 comments · Fixed by #17683

Comments

@ZecosMAX
Copy link

Describe the bug

I've updated my project from Avalonia 11.0.9 to 11.2.2 and my visual tree got broken.
When i got to check out what's actually happening, in the logs the following line appeared:

[Binding]An error occurred binding 'ItemsSource' to 'Satellites' at 'Satellites': 'Unable to cast object of type 'Avalonia.Yuujin.SatTrack.ViewModels.MainWindowViewModel' to type 'Avalonia.Yuujin.SatTrack.ViewModels.HomeViewModel'.' (ItemsControl #21626090)
[Binding]An error occurred binding '__AvaloniaReserved::Classes::Hidden' to '!ShowDetails' at 'ShowDetails': 'Unable to cast object of type 'Avalonia.Yuujin.SatTrack.ViewModels.MainWindowViewModel' to type 'Avalonia.Yuujin.SatTrack.ViewModels.HomeViewModel'.' (Border #60417086)

And many-many more of similar messages in nested controls.
Resulting in the actual controls' properties not being binded to anything.

'Strange' -- i thought, because in AXAML i am explicitly stating that i want the 'HomeView' to have bind it's DataContext to the property MainWindowViewModel.HomeViewModel

MainView.axaml

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
			 xmlns:themes="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"
			 xmlns:views="clr-namespace:Avalonia.Yuujin.SatTrack.Views"
             xmlns:vm="clr-namespace:Avalonia.Yuujin.SatTrack.ViewModels"
             xmlns:md="clr-namespace:Avalonia.Yuujin.SatTrack.ViewModels"
             mc:Ignorable="d" d:DesignWidth="392.73" d:DesignHeight="791.64"
             x:Class="Avalonia.Yuujin.SatTrack.Views.MainView"
             x:DataType="vm:MainWindowViewModel">
	<Design.DataContext>
		<vm:MainWindowViewModel />
	</Design.DataContext>
	
	<Grid Name="MainContainer" RowDefinitions="1*, Auto">
		<TabControl TabStripPlacement="Bottom" SelectedIndex="{Binding SelectedTabIndex}">
			<TabItem>
				<TabItem.Header>
					<StackPanel Orientation="Vertical" Margin="0, 5, 0, 3">
						<Image Height="24" Source="/Assets/home.png"/>
						<TextBlock Text="Home"/>
					</StackPanel>
				</TabItem.Header>
				<views:HomeView DataContext="{Binding HomeViewModel}"/>
			</TabItem>
...
...

I've looked up other issues (#17592)
And thought that similarly, it may have been my own mistake, but no. In the HomeView.axaml there isn't a single trace referencing MainWindowViewModel

HomeView.axaml

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
			 xmlns:themes="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"
             xmlns:c="clr-namespace:Avalonia.Yuujin.SatTrack.Controls"
             xmlns:vm="clr-namespace:Avalonia.Yuujin.SatTrack.ViewModels"
             xmlns:m="clr-namespace:Avalonia.Yuujin.SatTrack.Models"
             xmlns:cv="clr-namespace:Avalonia.Yuujin.SatTrack.Converters"
             mc:Ignorable="d" d:DesignWidth="392.73" d:DesignHeight="791.64"
             x:Class="Avalonia.Yuujin.SatTrack.Views.HomeView"
			 x:DataType="vm:HomeViewModel">

	<Design.DataContext>
		<vm:HomeViewModel>
			<vm:HomeViewModel.SelectedSatellite>
				<m:Satellite 
					Altitude="3.455735"
					Latitude="66.66666"
					Longitude="-148.2231"/>
			</vm:HomeViewModel.SelectedSatellite>
			<vm:HomeViewModel.MapScale>0.15</vm:HomeViewModel.MapScale>
		</vm:HomeViewModel>
	</Design.DataContext>

	<UserControl.Resources>
		<cv:DegreeToMinutesRepresentationConverter x:Key="DegreesConverter"/>
	</UserControl.Resources>

	<Grid>
		<ScrollViewer VerticalScrollBarVisibility="Hidden">
			<ItemsControl ItemsSource="{Binding Satellites}">
				<ItemsControl.ItemTemplate>
					<DataTemplate>
						<Border Margin="0,5" BoxShadow="0 5 5 0 #181818">
							<c:SatelliteSummary Height="64" DataContext="{Binding}"/>
						</Border>
					</DataTemplate>
				</ItemsControl.ItemTemplate>
			</ItemsControl>
		</ScrollViewer>

		<Border Background="{StaticResource MaterialPaperBrush}" 
				Classes.Hidden="{Binding !ShowDetails}" 
				IsVisible="{Binding ShowDetails}"
				PointerPressed="DetailsPointerPressed">
			<Border.Styles>
				<Style Selector="Border">
					<Setter Property="RenderTransform" Value="translate(0px, 0px)"/>
				</Style>
				<Style Selector="Border.Hidden">
					<Setter Property="RenderTransform" Value="translate(393px, 0px)"/>
				</Style>
			</Border.Styles>
			<Border.Transitions>
				<Transitions>
					<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.5" Easing="CubicEaseOut"/>
				</Transitions>
			</Border.Transitions>

			<Grid RowDefinitions="48, Auto, 1*" ClipToBounds="True" ShowGridLines="False">
				<Grid.Styles>
					<Style Selector="TextBlock">
						<Setter Property="FontSize" Value="20"/>
						<Setter Property="FontFamily" Value="{StaticResource SourceCodePro}"/>
					</Style>
				</Grid.Styles>
				
				<Button HorizontalAlignment="Left" VerticalAlignment="Stretch" Command="{Binding HideDetails}" Width="48" Classes="Flat" CornerRadius="0">
					<Image Source="/Assets/arrow-left.png" Width="24"/>
				</Button>
				<Button HorizontalAlignment="Right" VerticalAlignment="Stretch" Command="{Binding ShowDetailsSettings}" Width="48" Classes="Flat" CornerRadius="0">
					<Image Source="/Assets/format-list-bulleted.png" Width="24"/>
				</Button>
				<TextBlock Text="Details: NOAA-19" VerticalAlignment="Center" Margin="48,0,0,2" FontSize="22"/>

				<StackPanel Orientation="Vertical" Grid.Row="1">
					<TextBlock Text="{Binding SelectedSatellite.Altitude, StringFormat='Altitude:  {0:000.000} km'}" VerticalAlignment="Center" Margin="36,0,0,2"/>
					<TextBlock Text="{Binding SelectedSatellite.Latitude, 
							   StringFormat='Latitude:  {0}', 
							   Converter={StaticResource DegreesConverter}
							   ConverterParameter=Latitude}" VerticalAlignment="Center" Margin="36,0,0,2"/>
					
					<TextBlock Text="{Binding SelectedSatellite.Longitude, 
							   StringFormat='Longitude: {0}', 
							   Converter={StaticResource DegreesConverter}
							   ConverterParameter=Longitude}" VerticalAlignment="Center" Margin="36,0,0,2"/>
					
					<TextBlock Text="--------------------------" VerticalAlignment="Center" Margin="36,0,0,2"/>
					<TextBlock Text="{Binding SelectedSatellite.OrbitalSpeed, StringFormat='Oribtal speed: {0:0.00000} km/s'}" VerticalAlignment="Center" Margin="36,0,0,2"/>
					<TextBlock Text="Radial speed:  7.55300 km/s" VerticalAlignment="Center" Margin="36,0,0,2"/>
					<TextBlock Text="--------------------------" VerticalAlignment="Center" Margin="36,0,0,2"/>
					<TextBlock Text="Azimuth:   150.23° (SSE)" VerticalAlignment="Center" Margin="36,0,0,2"/>
					<TextBlock Text="Elevation:  20.23°" VerticalAlignment="Center" Margin="36,0,0,2"/>
					<TextBlock Text="--------------------------" VerticalAlignment="Center" Margin="36,0,0,2"/>
					<TextBlock Text="Next pass:     01h 22m 00s" VerticalAlignment="Center" Margin="36,0,0,2"/>
					<TextBlock Text="Max Elevation: 88.12°" VerticalAlignment="Center" Margin="36,0,0,2"/>
					<TextBlock Text="--------------------------" VerticalAlignment="Center" Margin="36,0,0,2"/>
				</StackPanel>

				<Border Background="#1E1E1E" Grid.Row="2">
					<Canvas x:Name="MapCanvas" Background="#003243" ClipToBounds="True" 
							PointerWheelChanged="Canvas_PointerWheelChanged"
							PointerPressed="Canvas_PointerPressed_1"
							PointerReleased="Canvas_PointerReleased"
							PointerMoved="Canvas_PointerMoved"
							Gestures.Pinch="Canvas_Pinch"
							Gestures.PinchEnded="Canvas_PinchEnd">
						<Canvas.GestureRecognizers>
							<PinchGestureRecognizer/>
						</Canvas.GestureRecognizers>

						<Image 
							x:Name="MapImage"
							Source="/Assets/miller2.png" 
							Canvas.Top="{Binding ImgPointPosY}" 
							Canvas.Left="{Binding ImgPointPosX}" 
							Width="{Binding CanvasMapImageWidth}"/>
						
						
						<ItemsControl
							ItemsSource="{Binding SatellitePathPoints}"
							Canvas.Top="{Binding ImgPointPosY}"
							Canvas.Left="{Binding ImgPointPosX}" >

							<ItemsControl.ItemsPanel>
								<ItemsPanelTemplate>
									<Grid/>
								</ItemsPanelTemplate>
							</ItemsControl.ItemsPanel>

							<ItemsControl.ItemTemplate>
								<DataTemplate>
									<Polyline Points="{Binding}" Stroke="#F06548" StrokeThickness="3"/>
								</DataTemplate>
							</ItemsControl.ItemTemplate>
						</ItemsControl>
						
						<Ellipse Fill="Red" 
								 Width="10" Height="10"
								 RenderTransform="translate(-5px, -5px)"
								 Canvas.Top="{Binding SatPointPosY}" Canvas.Left="{Binding SatPointPosX}"/>
						
						<Polygon Points="{Binding LitAreaPoints}" Stroke="DarkBlue" StrokeThickness="1" 
								 Fill="#60F06548" 
								 Canvas.Top="{Binding ImgPointPosY}"
								 Canvas.Left="{Binding ImgPointPosX}"/>
					</Canvas>
				</Border>

				<Border 
					Grid.Row="0" 
					Grid.RowSpan="2" 
					Background="#1E1E1E"
					HorizontalAlignment="Right"
					VerticalAlignment="Top"
					Classes.Hidden="{Binding !ShowDetailsMenu}"
					IsVisible="{Binding ShowDetailsMenu}">
					<Border.Styles>
						<Style Selector="Border">
							<Setter Property="RenderTransform" Value="translate(0px, 0px)"/>
						</Style>
						<Style Selector="Border.Hidden">
							<Setter Property="RenderTransform" Value="translate(300px, 0px)"/>
						</Style>
					</Border.Styles>
					<Border.Transitions>
						<Transitions>
							<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.5" Easing="CubicEaseOut"/>
						</Transitions>
					</Border.Transitions>

					<StackPanel>
						<StackPanel.Styles>
							<Style Selector="Button">
								<Setter Property="Background" Value="Transparent"/>
								<Setter Property="CornerRadius" Value="0"/>
							</Style>
							<Style Selector="Button /template/ ContentPresenter#PART_ContentPresenter">
								<Setter Property="HorizontalContentAlignment" Value="Left"/>
							</Style>
							<Style Selector="TextBlock">
								<Setter Property="Foreground" Value="#FFFFFF"/>
								<Setter Property="FontSize" Value="18"/>
								<Setter Property="TextAlignment" Value="Left"/>
								<Setter Property="Margin" Value="12,6"/>
							</Style>
						</StackPanel.Styles>

						<Grid>
							<TextBlock>Update period: 01s</TextBlock>
							<Button Classes="Flat"/>
						</Grid>
						<Grid>
							<TextBlock>SGP4 period: 60s</TextBlock>
							<Button Classes="Flat"/>
						</Grid>
						<Grid>
							<TextBlock>Update SGP4</TextBlock>
							<Button Classes="Flat"/>
						</Grid>
						<Grid>
							<TextBlock>Update TLE</TextBlock>
							<Button Classes="Flat"/>
						</Grid>
						<Grid>
							<TextBlock>Remove Record</TextBlock>
							<Button Classes="Flat" Command="{Binding RemoveSatRecord}"/>
						</Grid>
					</StackPanel>
				</Border>
				
			</Grid>
		</Border>
	</Grid>
</UserControl>

And the actual property also has the appropriate type:

private HomeViewModel homeViewModel;
public HomeViewModel HomeViewModel { get => homeViewModel; set => this.RaiseAndSetIfChanged(ref homeViewModel, value); }

So i went do debug, subscribed to event DataContextChanged in the HomeView.axaml.cs class like so:

public HomeView()
{
    InitializeComponent();

    this.DataContextChanged += HomeView_DataContextChanged;
}

private void HomeView_DataContextChanged(object? sender, System.EventArgs e)
{
    if (DataContext is HomeViewModel vm)
    {
        ;
    }
}

in the debugger i found that the HomeView's DataConext is actually being set to the MainWindowViewModel
image
what? how? why?, -- i went and check the stack trace, to find out where that has happened.

I found that it's happening in the setter of MainWindow.DataConext in the OnFrameworkInitializationComplete method in App.cs class
image
I've additionally made a sanity check that MainWindow is not, in fact, a HomeView

After that, the DataContext is being set properly. But all of the errors are already happened.
image

And so, as the result, the DataContext of the controls is being set properly at the end of the day, but the bindings on the controls properties are broken, and are not resolved automatically on DataContext change

HomeView's DataContext is set properly
image

Custom control's DataContext is set properly
image

The data is present
image

But the Text property on TextBlock inside a custom control is Unset
image

To Reproduce

  1. Create a parent View or a Window
  2. Create a child view with controls
  3. Bind the child view's control data to the data in the child's viewmodel
  4. Place the child view in the parent view or a window and explicitly set the child's DataContext to property declared in the parent's viewmodel
  5. Set the parent view/window data context to new object in OnFrameworkInitializationCompleted in App.cs

Expected behavior

The children controls DataContext not being set to the parent DataContext, but immediately to the appropriate property, if it is present.
--or--
The binding being restored when the DataContext changes to the object with appropriate type.

Avalonia version

11.2.2

OS

Windows, Android

Additional context

SatteliteSummary.axaml

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:m="clr-namespace:Avalonia.Yuujin.SatTrack.Models"
             mc:Ignorable="d" d:DesignWidth="392.7" d:DesignHeight="64"
			 x:DataType="m:Satellite"
             x:Class="Avalonia.Yuujin.SatTrack.Controls.SatelliteSummary"
			 ClipToBounds="True">

	<Design.DataContext>
		<m:Satellite>
			<m:Satellite.SatcatRecord>
				<m:SatcatRecord 
					ObjectName="SUOMI UWP [+]"
					NORADCatId="12345"/>
			</m:Satellite.SatcatRecord>
		</m:Satellite>
	</Design.DataContext>
	
	<Grid 
		ShowGridLines="False"
		ClipToBounds="True"
		ColumnDefinitions="6, 48, 1*, 48, 6">
		
		<Image Grid.Column="1"
			   Height="32"
			   Width="32"
			   Source="/Assets/satellite-variant.png"/>

		<Grid Grid.Column="2"
			  ShowGridLines="False"
			  ClipToBounds="True"
			  RowDefinitions="Auto, 1*"
			  Margin="0,5">
			<Grid.Styles>
				<Style Selector="TextBlock">
					<Setter Property="FontSize" Value="15"/>
					<Setter Property="FontFamily" Value="{StaticResource SourceCodePro}"/>
				</Style>
			</Grid.Styles>
			
			<TextBlock Text="{Binding SatcatRecord.ObjectName}" 
					   HorizontalAlignment="Left" 
					   VerticalAlignment="Center" 
					   Margin="9,0,0,0"/>
			<TextBlock Text="{Binding SatcatRecord.NORADCatId, StringFormat='NORAD: {0}'}" 
					   HorizontalAlignment="Right" 
					   VerticalAlignment="Center" 
					   Background="{StaticResource MaterialPaperBrush}"
					   Margin="0,0,3,0"/>

			<TextBlock Text="{Binding QuickStatus}"
					   Grid.Row="1"
					   FontSize="20"
					   Foreground="{Binding QuickStatusColor}"
					   HorizontalAlignment="Right"
					   VerticalAlignment="Top"
					   Margin="0,-4,3,0"/>
			<TextBlock Text="{Binding QuickElevation}"
					   Grid.Row="1"
					   FontSize="12"
					   Foreground="{Binding QuickStatusColor}"
					   HorizontalAlignment="Right"
					   VerticalAlignment="Bottom"
					   Margin="0,0,3,1"/>

		</Grid>

		<Button
			Grid.Column="3"
			CornerRadius="100"
			Height="40"
			Width="40"
			Classes.Flat="True"
			Margin="2,0,0,0"
			Command="{Binding Details}">
			<Image
				Height="50"
				Width="70"
				Source="/Assets/menu-right.png"/>
		</Button>
	</Grid>
</UserControl>
@ZecosMAX ZecosMAX added the bug label Nov 29, 2024
@timunie
Copy link
Contributor

timunie commented Nov 30, 2024

Please extract a minimum sample and file it here. We cannot debug into complete Apps for free. Thx for your understanding.

@ZecosMAX
Copy link
Author

@timunie wdym for free? 💀 I've literally provided steps to reproduce. Oh well.
https://github.com/ZecosMAX/Avalonia.CastErrorSample

@ZecosMAX
Copy link
Author

It's the same behavior, the controls inside ChildView lose their bindings and don't display anything, despite working properly on their own
image
image

@ZecosMAX
Copy link
Author

This actually works properly if you don't set the data context on the ChildView

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <Border Margin="0,5" BoxShadow="0 5 5 0 #181818">
            <v:ChildView />
        </Border>
    </DataTemplate>
</ItemsControl.ItemTemplate>

@stevemonaco
Copy link
Contributor

stevemonaco commented Nov 30, 2024

wdym for free? 💀 I've literally provided steps to reproduce. Oh well.

Reading through large descriptions and screenshots puts an increased burden on maintainers leading to less work being done on Avalonia and more work being done on communicating+reproducing. When required, those scenarios can require paid support. That said, the minimal repro provided was good.

There's no need to pass-down DataContext as it's inherited down the tree unless otherwise set. The behavior is a bit unexpected though across different scenarios and the DataContext-related events called:

  1. <v:ChildView /> (shows correct)
    OnDataContextBeginUpdate -> OnDataContextChanged -> OnDataContextEndUpdate
  2. <v:ChildView DataContext="{Binding}" /> (shows empty items)
    OnDataContextBeginUpdate
  3. <v:ChildView DataContext="{Binding $parent[Border].DataContext}" /> (shows correct data)
    OnDataContextBeginUpdate -> OnDataContextChanged -> OnDataContextEndUpdate

Personally, I only use case 1. I very rarely assign DataContext via XAML in general, relying upon collection controls and ContentControl to do that for me. There are some cases where you're trying to reduce tree size and it makes sense to skip ContentControl for an explicit view though.

Not sure what the underlying reason why OnDataContextChanged doesn't happen for case 2, but I think that leads to lack of invalidation down the tree. I've ran this case on WPF and it works correctly (both in display and raising DataContextChanged), so there's some issue in Avalonia.

@timunie
Copy link
Contributor

timunie commented Nov 30, 2024

This seems to be a duplicate #17643 . At the moment {Binding} don't work correctly. I'll leave this one open and close the other due to better title 🙂

@rabbitism
Copy link
Contributor

#17647

same

@KarenArzumanyan
Copy link

At the moment {Binding} don't work correctly.

Is this a regression (11.0.10 -> 11.2.2) that is planned to be fixed?

@timunie
Copy link
Contributor

timunie commented Dec 2, 2024

Planned but no due date, since there is a really easy way around it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants