Xamarin Forms Framework 中的自定义控件导致空引用异常
Custom Control Causing Null Reference Exception in Xamarin Forms Framework
我构建了一个自定义 SVG 按钮,这样我就可以拥有一个带有文本和 SVG 图像的 Xamarin Forms 按钮。我让它运行良好,然后我将我的项目迁移到 .NET Standard 2.0。
现在,当更改控件上的文本时,我在 Xamarin Forms Framework 的 StackLayout 的 ListDictionaryInternal 上的 LayoutChildIntoBoundingRegion 方法中收到空引用异常。
这里是控件。很简单。
using System.Windows.Input;
using FFImageLoading.Svg.Forms;
using Core.Interfaceses;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace CustomControls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public class SVGImageButton : ContentView
{
#region Member Variables
private readonly Button _button;
private readonly SvgCachedImage _svgImage;
#endregion
#region Bindible Properties
public static BindableProperty ButtonPressedCommandProperty = BindableProperty.Create(nameof(ButtonPressedCommand), typeof(ICommand), typeof(SVGImageButton));
public ICommand ButtonPressedCommand
{
get
{
return (ICommand)GetValue(ButtonPressedCommandProperty);
}
set
{
SetValue(ButtonPressedCommandProperty, value);
}
}
public static BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(SVGImageButton));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static BindableProperty ButtonTextProperty = BindableProperty.Create(nameof(ButtonText), typeof(string), typeof(SVGImageButton), string.Empty, BindingMode.TwoWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonText = newValue.ToString();
});
public string ButtonText
{
get
{
return _button.Text;
}
set
{
_button.Text = value;
}
}
public static BindableProperty ButtonBackgroundColorProperty = BindableProperty.Create(nameof(ButtonBackgroundColor), typeof(Color), typeof(View), Color.Fuchsia, BindingMode.OneWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonBackgroundColor = (Color)newValue;
});
public Color ButtonBackgroundColor
{
get
{
return _button.BackgroundColor;
}
set
{
_button.BackgroundColor = value;
}
}
public static BindableProperty ButtonTextColorProperty = BindableProperty.Create(nameof(ButtonTextColor), typeof(Color), typeof(View), Color.Fuchsia, BindingMode.OneWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonTextColor = (Color)newValue;
});
public Color ButtonTextColor
{
get
{
return _button.TextColor;
}
set
{
_button.TextColor = value;
}
}
public static BindableProperty ButtonStyleProperty = BindableProperty.Create(nameof(ButtonBackgroundColor), typeof(Style), typeof(View), null, BindingMode.OneWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonStyle = (Style)newValue;
});
public Style ButtonStyle
{
get
{
return _button.Style;
}
set
{
_button.Style = value;
}
}
public static BindableProperty ButtonBorderWidthProperty = BindableProperty.Create(nameof(ButtonBorderWidth), typeof(double), typeof(View), 0.0, BindingMode.OneWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonBorderWidth = (double)newValue;
});
public double ButtonBorderWidth
{
get
{
return _button.BorderWidth;
}
set
{
_button.BorderWidth = value;
}
}
public static BindableProperty ButtonBorderColorProperty = BindableProperty.Create(nameof(ButtonBorderColor), typeof(Color), typeof(View), Color.Fuchsia, BindingMode.OneWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonBorderColor = (Color)newValue;
});
public Color ButtonBorderColor
{
get
{
return _button.BorderColor;
}
set
{
_button.BorderColor = value;
}
}
#endregion
#region Properties
public string SVGImageName { get; set; }
#endregion
#region Life Cycle
protected override void OnParentSet()
{
base.OnParentSet();
_button.Text = ButtonText;
_svgImage.Source = SvgImageSource.FromResource(SVGImageName);
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
_button.HorizontalOptions = (Parent as View).HorizontalOptions;
_button.VerticalOptions = (Parent as View).VerticalOptions;
if (_button.Text != null)
{
var size = DependencyService.Get<ITextMeter>().MeasureTextSize(_button.Text.TrimEnd(), 200, _button.FontSize);
WidthRequest = size.Width + 60 + _button.Height;
}
return base.OnMeasure(widthConstraint, heightConstraint);
}
public SVGImageButton()
{
var content = new RelativeLayout();
_button = new Button { BackgroundColor = Color.Gray, TextColor = Color.Black };
_svgImage = new SvgCachedImage { IsEnabled = false };
content.Children.Add(_button,
Constraint.RelativeToParent((parent) =>
{
return parent.X;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Y;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Width;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height;
}));
content.Children.Add(_svgImage,
Constraint.RelativeToParent((parent) =>
{
return parent.Width - (parent.Height / 2) - (parent.Height / 4);
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height - (parent.Height / 2) - (parent.Height / 4);
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height / 2;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height / 2;
}));
Content = content;
_button.Clicked += (sender, e) =>
{
ButtonPressedCommand?.Execute(CommandParameter);
};
}
#endregion
}
}
下面是控件在 XAML 中的用法。
<customcontrols:SVGImageButton
x:Name="TypeSelectionButton"
HorizontalOptions="Start"
VerticalOptions="Center"
ButtonPressedCommand="{Binding TypeButtonClickedCommand}"
SVGImageName="SVGImages.ic_triangle_down.svg"
CommandParameter="{x:Reference TypeSelectionButton}"
ButtonText="{Binding SearchTypeButtonText}"
ButtonBackgroundColor="{Binding ButtonBackgroundColor}"/>
在迁移到 .NET Standard 2.0 之前,当文本更改时,控件会调整大小,现在会崩溃。
非常感谢任何帮助我了解正在发生的事情!
好的,在 SO 中查看我的代码后,我能够看到错误。在 OnMeasure 回调中,我正在测量文本,然后设置 SVGButton 的 ContentView 的 WidthRequest。我把它改成这样,现在它可以正常工作了。
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
_button.HorizontalOptions = (Parent as View).HorizontalOptions;
_button.VerticalOptions = (Parent as View).VerticalOptions;
var width = 0d;
if (_button.Text != null)
{
var size = DependencyService.Get<ITextMeter>().MeasureTextSize(_button.Text.TrimEnd(), 200, _button.FontSize);
width = size.Width + 40 + _button.Height;
}
return base.OnMeasure(width, heightConstraint);
}
我构建了一个自定义 SVG 按钮,这样我就可以拥有一个带有文本和 SVG 图像的 Xamarin Forms 按钮。我让它运行良好,然后我将我的项目迁移到 .NET Standard 2.0。
现在,当更改控件上的文本时,我在 Xamarin Forms Framework 的 StackLayout 的 ListDictionaryInternal 上的 LayoutChildIntoBoundingRegion 方法中收到空引用异常。
这里是控件。很简单。
using System.Windows.Input;
using FFImageLoading.Svg.Forms;
using Core.Interfaceses;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace CustomControls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public class SVGImageButton : ContentView
{
#region Member Variables
private readonly Button _button;
private readonly SvgCachedImage _svgImage;
#endregion
#region Bindible Properties
public static BindableProperty ButtonPressedCommandProperty = BindableProperty.Create(nameof(ButtonPressedCommand), typeof(ICommand), typeof(SVGImageButton));
public ICommand ButtonPressedCommand
{
get
{
return (ICommand)GetValue(ButtonPressedCommandProperty);
}
set
{
SetValue(ButtonPressedCommandProperty, value);
}
}
public static BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(SVGImageButton));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static BindableProperty ButtonTextProperty = BindableProperty.Create(nameof(ButtonText), typeof(string), typeof(SVGImageButton), string.Empty, BindingMode.TwoWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonText = newValue.ToString();
});
public string ButtonText
{
get
{
return _button.Text;
}
set
{
_button.Text = value;
}
}
public static BindableProperty ButtonBackgroundColorProperty = BindableProperty.Create(nameof(ButtonBackgroundColor), typeof(Color), typeof(View), Color.Fuchsia, BindingMode.OneWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonBackgroundColor = (Color)newValue;
});
public Color ButtonBackgroundColor
{
get
{
return _button.BackgroundColor;
}
set
{
_button.BackgroundColor = value;
}
}
public static BindableProperty ButtonTextColorProperty = BindableProperty.Create(nameof(ButtonTextColor), typeof(Color), typeof(View), Color.Fuchsia, BindingMode.OneWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonTextColor = (Color)newValue;
});
public Color ButtonTextColor
{
get
{
return _button.TextColor;
}
set
{
_button.TextColor = value;
}
}
public static BindableProperty ButtonStyleProperty = BindableProperty.Create(nameof(ButtonBackgroundColor), typeof(Style), typeof(View), null, BindingMode.OneWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonStyle = (Style)newValue;
});
public Style ButtonStyle
{
get
{
return _button.Style;
}
set
{
_button.Style = value;
}
}
public static BindableProperty ButtonBorderWidthProperty = BindableProperty.Create(nameof(ButtonBorderWidth), typeof(double), typeof(View), 0.0, BindingMode.OneWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonBorderWidth = (double)newValue;
});
public double ButtonBorderWidth
{
get
{
return _button.BorderWidth;
}
set
{
_button.BorderWidth = value;
}
}
public static BindableProperty ButtonBorderColorProperty = BindableProperty.Create(nameof(ButtonBorderColor), typeof(Color), typeof(View), Color.Fuchsia, BindingMode.OneWay, propertyChanged: (bindable, oldValue, newValue) =>
{
if (newValue == null) return;
var control = (SVGImageButton)bindable;
control.ButtonBorderColor = (Color)newValue;
});
public Color ButtonBorderColor
{
get
{
return _button.BorderColor;
}
set
{
_button.BorderColor = value;
}
}
#endregion
#region Properties
public string SVGImageName { get; set; }
#endregion
#region Life Cycle
protected override void OnParentSet()
{
base.OnParentSet();
_button.Text = ButtonText;
_svgImage.Source = SvgImageSource.FromResource(SVGImageName);
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
_button.HorizontalOptions = (Parent as View).HorizontalOptions;
_button.VerticalOptions = (Parent as View).VerticalOptions;
if (_button.Text != null)
{
var size = DependencyService.Get<ITextMeter>().MeasureTextSize(_button.Text.TrimEnd(), 200, _button.FontSize);
WidthRequest = size.Width + 60 + _button.Height;
}
return base.OnMeasure(widthConstraint, heightConstraint);
}
public SVGImageButton()
{
var content = new RelativeLayout();
_button = new Button { BackgroundColor = Color.Gray, TextColor = Color.Black };
_svgImage = new SvgCachedImage { IsEnabled = false };
content.Children.Add(_button,
Constraint.RelativeToParent((parent) =>
{
return parent.X;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Y;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Width;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height;
}));
content.Children.Add(_svgImage,
Constraint.RelativeToParent((parent) =>
{
return parent.Width - (parent.Height / 2) - (parent.Height / 4);
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height - (parent.Height / 2) - (parent.Height / 4);
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height / 2;
}),
Constraint.RelativeToParent((parent) =>
{
return parent.Height / 2;
}));
Content = content;
_button.Clicked += (sender, e) =>
{
ButtonPressedCommand?.Execute(CommandParameter);
};
}
#endregion
}
}
下面是控件在 XAML 中的用法。
<customcontrols:SVGImageButton
x:Name="TypeSelectionButton"
HorizontalOptions="Start"
VerticalOptions="Center"
ButtonPressedCommand="{Binding TypeButtonClickedCommand}"
SVGImageName="SVGImages.ic_triangle_down.svg"
CommandParameter="{x:Reference TypeSelectionButton}"
ButtonText="{Binding SearchTypeButtonText}"
ButtonBackgroundColor="{Binding ButtonBackgroundColor}"/>
在迁移到 .NET Standard 2.0 之前,当文本更改时,控件会调整大小,现在会崩溃。
非常感谢任何帮助我了解正在发生的事情!
好的,在 SO 中查看我的代码后,我能够看到错误。在 OnMeasure 回调中,我正在测量文本,然后设置 SVGButton 的 ContentView 的 WidthRequest。我把它改成这样,现在它可以正常工作了。
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
_button.HorizontalOptions = (Parent as View).HorizontalOptions;
_button.VerticalOptions = (Parent as View).VerticalOptions;
var width = 0d;
if (_button.Text != null)
{
var size = DependencyService.Get<ITextMeter>().MeasureTextSize(_button.Text.TrimEnd(), 200, _button.FontSize);
width = size.Width + 40 + _button.Height;
}
return base.OnMeasure(width, heightConstraint);
}