我正在尝试添加一个通用控件,我希望出现在应用程序中每个内容页面的底部(这些都将位于 TabBar 内)。我在 App.xaml 中创建了一个 ControlTemplate,并且出现了放置在底部的选取器,但 ItemsSource 属性绑定不起作用(没有可见的项目)。
我不知道如何让它发挥作用。我是 Xamarin/MAUI 的新手,如果我要实现这一目标的方向错误,我愿意接受不同方法的建议。
我尝试在 XAML 中使用
TemplateBinding
而不是 Binding
,并且我还在 App.xaml.cs 和 AppShell.xaml.cs 代码隐藏文件中放置了相同的属性,以防绑定被重定向到那里,这没有什么区别。我还从 Environments
属性开始,只是类型为 Env[]
,然后切换到 ObservableCollection
作为故障排除措施(即使集合显然是静态的)。
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:My.App"
x:Class="My.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
<ResourceDictionary>
<ControlTemplate x:Key="InputPageTemplate">
<VerticalStackLayout BindingContext="{Binding Source={RelativeSource TemplatedParent}}">
<ContentPresenter />
<!-- ******************** BINDINGS NOT WORKING ******************** -->
<Picker ItemsSource="{Binding Environments}"
SelectedItem="{Binding AppConfig.Environment}" />
</VerticalStackLayout>
</ControlTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
public class MyPage : ContentPage
{
public MyPage()
{
if (!Application.Current!.Resources.TryGetValue("InputPageTemplate", out var resource) ||
resource is not ControlTemplate template)
{
throw new Exception("Missing InputPageTemplate control template");
}
var appConfig = new AppConfig { Environment = Env.B };
ViewModel = new MyViewModel(appConfig);
BindingContext = ViewModel;
ControlTemplate = template;
}
}
public class MyViewModel
{
public MyViewModel(AppConfig appConfig)
{
AppConfig = appConfig;
}
public AppConfig AppConfig { get; }
public ObservableCollection<Env> Environments => new(Enum.GetValues<Env>());
}
public class AppConfig : INotifyPropertyChanged
{
private Env _environment;
public Env Environment
{
get => _environment;
set
{
_environment = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string name = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public enum Env
{
A,
B
}
让模板与内容页面共享绑定上下文的方法是在绑定中包含路径,如下所示:
<VerticalStackLayout
BindingContext="{Binding Source={RelativeSource TemplatedParent}, Path=BindingContext}">
<!-- Add this: ^^^^^^^^^^^^^^^^^^^ -->
好吧,这会很长。
当您想在 ControlTemplate 中绑定某些内容时,不要直接绑定它。 您必须使用不同的来源。
有几种方法可以做到这一点。一种是使用TemplateBinding 这个词。 另一种是将 BindingContext 设置为模板化父级。
例如:
<Control Template...
<Grid BindingContext="{Binding Source={RelativeSource TemplatedParent}"...
<Button Command="{Binding MyCommand}"...
当您按下该按钮时,绑定将执行 MyCommand,无论该控件在运行时是什么。
因此,如果在您的 ViewModel 中您有 GetDataCommand,您将在您的 View 中执行此操作:
<mycontrols:MyView MyCommand="{Binding GetDataCommand}...
这里的处理是让这个自定义控件类 MyView 具有 BindableProperty。这将允许您在 ViewModel 和 ControlTemplate 之间进行绑定。
public class MyView : ContentView{
public static readonly MyCommandProperty = ...
public ICommand MyCommand...
}
当我不确定某件事是如何完成时,我通常会点击F12并浏览平台代码。首先,您了解一切是如何运作的,其次您学习如何自己做。
另外,我建议您使用依赖注入,并限制构造函数的使用。我还推荐 CommunityToolkit.MVVM。它将节省您实施 INotifyPropertyChanged 的时间。 (以及错误操作可能导致的错误)。