Avalonia自定义UserControl绑定抛出System.InvalidCastException问题
问题场景
刚搭建C# Avalonia项目,测试自定义UserControl时遇到问题:想给自定义控件添加可绑定属性,但主窗口中绑定该属性时不生效,还抛出System.InvalidCastException异常,而相同数据源绑定到标准Label却能正常工作。
现有代码
1. MainWindow.axaml
<Window 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:c="using:My.Avalonia.Controls" xmlns:vm="using:My.Avalonia.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="My.Avalonia.Views.MainWindow" Icon="/Assets/avalonia-logo.ico" Title="My.Avalonia" x:DataType="vm:MainViewModel" > <StackPanel> <c:MyControl Text="{Binding MyObject.DisplayName, Mode=OneWay}"/> <!-- 这段绑定正常工作: --> <Label Content="{Binding MyObject.DisplayName, Mode=OneWay}"/> </StackPanel>
2. 自定义控件MyControl
MyControl.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:c="using:My.Avalonia.Controls" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="My.Avalonia.Controls.MyControl" x:Name="iconControl" x:DataType="c:MyControl" > <Design.DataContext> <c:MyControl /> </Design.DataContext> <Label Content="{Binding Text, Mode=OneWay}" Background="Aqua"/> </UserControl>
MyControl.axaml.cs
using Avalonia; using Avalonia.Controls; using Avalonia.Media; using My.Avalonia.Models; using System.Collections.Specialized; using System.ComponentModel; namespace My.Avalonia.Controls { public partial class MyControl : UserControl, INotifyPropertyChanged { public MyControl() { InitializeComponent(); DataContext = this; } public static readonly StyledProperty<string?> TextProperty = AvaloniaProperty.Register<MyControl, string?>(nameof(Text)); public string? Text { get { return GetValue(TextProperty); } set { SetValue(TextProperty, value); } } } }
问题详情
- 自定义控件的
Text绑定完全不生效,标准Label绑定同一数据源正常显示,说明数据源MyObject.DisplayName有效。 - 调试时抛出
Exception thrown: 'System.InvalidCastException' in System.Private.CoreLib.dll异常,移除自定义控件的绑定后异常消失。 - 即使删除自定义控件内部的Label,异常依然存在,说明问题出在主窗口到自定义控件的绑定环节。
- 双方类型都是
string?,不理解为何会出现类型转换异常。
解决方案
问题出在三个关键错误上,逐一修正即可:
1. 移除构造函数中硬设置的DataContext = this
Avalonia中,自定义控件默认会继承父容器的DataContext(也就是MainWindow的MainViewModel)。你在构造函数里强制将DataContext设为控件自身,导致绑定解析逻辑混乱——外部绑定试图从MainViewModel查找Text属性失败后,触发类型转换异常。
删掉构造函数中的DataContext = this;代码。
2. 明确控件内部绑定的数据源
移除硬设的DataContext后,控件内部的Label需要明确绑定到控件自身的Text属性,避免和继承的DataContext混淆。可以通过ElementName指定:
修改MyControl.axaml中的Label绑定:
<Label Content="{Binding #iconControl.Text, Mode=OneWay}" Background="Aqua"/>
或者用RelativeSource方式:
<Label Content="{Binding Text, Mode=OneWay, RelativeSource={RelativeSource AncestorType=UserControl}}" Background="Aqua"/>
3. 移除不必要的INotifyPropertyChanged实现
你使用的是Avalonia的StyledProperty,这类依赖属性本身已经自带属性变更通知机制,不需要额外实现INotifyPropertyChanged接口,直接删掉相关代码即可。
修改后的MyControl.axaml.cs:
using Avalonia; using Avalonia.Controls; using Avalonia.Media; using My.Avalonia.Models; namespace My.Avalonia.Controls { public partial class MyControl : UserControl { public MyControl() { InitializeComponent(); } public static readonly StyledProperty<string?> TextProperty = AvaloniaProperty.Register<MyControl, string?>(nameof(Text)); public string? Text { get { return GetValue(TextProperty); } set { SetValue(TextProperty, value); } } } }
原理说明
Avalonia控件绑定分两种核心场景:
- 外部绑定:父容器给控件的依赖属性绑定数据源时,控件继承父容器的DataContext,绑定逻辑从父级DataContext中查找路径。
- 内部绑定:控件内部元素绑定自身属性时,必须明确指定绑定源为控件实例,避免和继承的DataContext混淆。
使用StyledProperty/DirectProperty时,Avalonia框架会自动处理属性变更通知,手动实现INotifyPropertyChanged反而可能引发逻辑冲突。
内容的提问来源于stack exchange,提问作者Hunv

