c# - 数据绑定的 WPF 组合框错误
问题描述
我是 C# 新手,我不断收到无法删除的错误。
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is 'RibbonGalleryItem' (Name=''); target property is 'HorizontalContentAlignment' (type 'HorizontalAlignment')
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=VerticalContentAlignment; DataItem=null; target element is 'RibbonGalleryItem' (Name=''); target property is 'VerticalContentAlignment' (type 'VerticalAlignment')
我的代码在下面,我无法准确判断是哪一行导致了错误,但我怀疑它与 RibbonRadioButtons 有关,因为如果我删除它们,我不会收到错误。只有在单击两个或更多单选按钮后才会出现错误。尽管样式表明它是多个 Refresh() 语句导致问题,但 ComboBoxItem的答案继续引发绑定错误,但我看不出如何避免这种情况。
谁能帮我解决这个问题?
XAML
<Grid>
<DockPanel>
<r:Ribbon DockPanel.Dock="Top" x:Name="Ribbon" SelectedIndex="0">
<r:RibbonGroup Header="Continent" Width="Auto">
<r:RibbonComboBox x:Name="CountryList" Height="Auto" VerticalAlignment="Center" HorizontalContentAlignment="Left">
<r:RibbonGallery x:Name="cbSelectedCountry" SelectedValue="{Binding SelectedCountry, Mode=TwoWay}" SelectedValuePath="DisplayName" >
<r:RibbonGalleryCategory x:Name="cbCountryList" ItemsSource="{Binding CountryView}" DisplayMemberPath="DisplayName" />
</r:RibbonGallery>
</r:RibbonComboBox>
<WrapPanel>
<r:RibbonRadioButton x:Name="All" Label="All" GroupName="ContinentGroup"
Height="Auto" Width="Auto" HorizontalAlignment="Left"
IsChecked="{Binding Path=All}">
</r:RibbonRadioButton>
<r:RibbonRadioButton x:Name="Africa" Label="Africa" GroupName="ContinentGroup"
Height="Auto" Width="Auto" HorizontalAlignment="Left"
IsChecked="{Binding Path=Africa}">
</r:RibbonRadioButton>
<r:RibbonRadioButton x:Name="America" Label="America" GroupName="ContinentGroup"
Height="Auto" Width="Auto" HorizontalAlignment="Left"
IsChecked="{Binding Path=America}">
</r:RibbonRadioButton>
</WrapPanel>
</r:RibbonGroup>
</r:Ribbon>
</DockPanel>
</Grid>
C#
public class MySettings : INotifyPropertyChanged
{
private readonly ObservableCollection<Country> countries;
private ContinentViewModel selectedContinent;
private static string selectedCountry;
private int selectedRadioGroup;
private ObservableCollection<ContinentViewModel> continents;
private ListCollectionView countryView;
public event PropertyChangedEventHandler PropertyChanged;
private bool _All;
private bool _Africa;
private bool _America;
public MySettings()
{
countries = new ObservableCollection<Country>(
new[]
{
new Country() { Continent = Continent.Africa, DisplayName = "Algeria" },
new Country() { Continent = Continent.Africa, DisplayName = "Egypt" },
new Country() { Continent = Continent.Africa, DisplayName = "Chad" },
new Country() { Continent = Continent.Africa, DisplayName = "Ghana" },
new Country() { Continent = Continent.America, DisplayName = "Canada" },
new Country() { Continent = Continent.America, DisplayName = "Greenland" },
new Country() { Continent = Continent.America, DisplayName = "Haiti" }
});
CountryView = (ListCollectionView)CollectionViewSource.GetDefaultView(countries);
CountryView.Filter += CountryFilter;
Continents = new ObservableCollection<ContinentViewModel>(Enum.GetValues(typeof(Continent)).Cast<Continent>().Select(c => new ContinentViewModel { Model = c }));
}
public bool All
{
get => _All;
set
{
_All = value;
CountryView.Refresh();
SelectedCountry = _All ? countries.FirstOrDefault().DisplayName : SelectedCountry;
OnPropertyChanged("All");
}
}
public bool Africa
{
get => _Africa;
set
{
_Africa = value;
CountryView.Refresh();
SelectedCountry = _Africa ? countries.Where(_ => _.Continent == Continent.Africa).FirstOrDefault().DisplayName : SelectedCountry;
OnPropertyChanged("Africa");
}
}
public bool America
{
get => _America;
set
{
_America = value;
CountryView.Refresh();
SelectedCountry = _America ? countries.Where(_ => _.Continent == Continent.America).FirstOrDefault().DisplayName : SelectedCountry;
OnPropertyChanged("America");
}
}
private bool CountryFilter(object obj)
{
var country = obj as Country;
if (country == null) return false;
if (All && !Africa && !America) return true;
else if (!All && Africa && !America) return country.Continent == Continent.Africa;
else if (!All && !Africa && America) return country.Continent == Continent.America;
return true;
}
public ObservableCollection<ContinentViewModel> Continents
{
get => continents;
set
{
continents = value;
OnPropertyChanged("Continents");
}
}
public ListCollectionView CountryView
{
get => countryView;
set
{
countryView = value;
OnPropertyChanged("CountryView");
}
}
public class Country
{
public string DisplayName { get; set; }
public Continent Continent { get; set; }
}
public enum Continent
{
All,
Africa,
America
}
public class ContinentViewModel
{
public Continent Model { get; set; }
public string DisplayName => Enum.GetName(typeof(Continent), Model);
}
public ContinentViewModel SelectedContinent
{
get => selectedContinent;
set
{
selectedContinent = value;
OnContinentChanged();
this.OnPropertyChanged("SelectedContinent");
}
}
private void OnContinentChanged()
{
CountryView.Refresh();
}
public int SelectedRadioGroup
{
get => selectedRadioGroup;
set
{
selectedRadioGroup = value;
OnPropertyChanged("SelectedRadioGroup");
}
}
public string SelectedCountry
{
get => selectedCountry;
set
{
if (selectedCountry == value) return;
selectedCountry = value;
OnPropertyChanged("SelectedCountry");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
解决方案
我使用 mvvmlight 并进行了一些更改以简化您的代码:
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:WpfTest"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<DockPanel>
<Ribbon DockPanel.Dock="Top" x:Name="Ribbon" SelectedIndex="0">
<RibbonGroup Header="Continent" Width="Auto">
<RibbonComboBox x:Name="CountryList" Height="Auto" VerticalAlignment="Center" HorizontalContentAlignment="Left">
<RibbonGallery x:Name="cbSelectedCountry" SelectedItem="{Binding SelectedCountry, Mode=TwoWay}">
<RibbonGalleryCategory x:Name="cbCountryList" ItemsSource="{Binding CountryView}" DisplayMemberPath="DisplayName" />
</RibbonGallery>
</RibbonComboBox>
<WrapPanel>
<RibbonRadioButton x:Name="All" Label="All" GroupName="ContinentGroup" Height="Auto" Width="Auto" HorizontalAlignment="Left" IsChecked="{Binding Path=All}"></RibbonRadioButton>
<RibbonRadioButton x:Name="Africa" Label="Africa" GroupName="ContinentGroup" Height="Auto" Width="Auto" HorizontalAlignment="Left" IsChecked="{Binding Path=Africa}"></RibbonRadioButton>
<RibbonRadioButton x:Name="America" Label="America" GroupName="ContinentGroup" Height="Auto" Width="Auto" HorizontalAlignment="Left" IsChecked="{Binding Path=America}"></RibbonRadioButton>
</WrapPanel>
</RibbonGroup>
</Ribbon>
</DockPanel>
</Grid>
我创建了一个 Country 类型的属性 SelectedCountry 来将组合框绑定到,而不是像您的代码那样设置一个字符串。
using GalaSoft.MvvmLight;
using System.Collections.ObjectModel;
using System.Windows.Data;
namespace WpfTest.ViewModel
{
/// <summary>
/// This class contains properties that the main View can data bind to.
/// <para>
/// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
/// </para>
/// <para>
/// You can also use Blend to data bind with the tool's support.
/// </para>
/// <para>
/// See http://www.galasoft.ch/mvvm
/// </para>
/// </summary>
public class MainViewModel : ViewModelBase
{
private readonly ObservableCollection<Country> countries;
private ListCollectionView _countryView;
public ListCollectionView CountryView
{
get { return _countryView; }
set { this.Set(ref _countryView, value); }
}
private Country _SelectedCountry;
public Country SelectedCountry
{
get { return _SelectedCountry; }
set { this.Set(ref _SelectedCountry, value); }
}
private bool _All;
public bool All
{
get { return _All; }
set
{
this.Set(ref _All, value);
CountryView.Refresh();
}
}
private bool _Africa;
public bool Africa
{
get { return _Africa; }
set
{
this.Set(ref _Africa, value);
if (SelectedCountry != null && SelectedCountry.Continent != Continent.Africa)
{
SelectedCountry = null;
}
CountryView.Refresh();
}
}
private bool _America;
public bool America
{
get { return _America; }
set
{
this.Set(ref _America, value);
if (SelectedCountry != null && SelectedCountry.Continent != Continent.America)
{
SelectedCountry = null;
}
CountryView.Refresh();
}
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
countries = new ObservableCollection<Country>(
new[]
{
new Country() { Continent = Continent.Africa, DisplayName = "Algeria" },
new Country() { Continent = Continent.Africa, DisplayName = "Egypt" },
new Country() { Continent = Continent.Africa, DisplayName = "Chad" },
new Country() { Continent = Continent.Africa, DisplayName = "Ghana" },
new Country() { Continent = Continent.America, DisplayName = "Canada" },
new Country() { Continent = Continent.America, DisplayName = "Greenland" },
new Country() { Continent = Continent.America, DisplayName = "Haiti" }
});
CountryView = (ListCollectionView)CollectionViewSource.GetDefaultView(countries);
CountryView.Filter += CountryFilter;
CountryView.Refresh();
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real"
////}
}
private bool CountryFilter(object obj)
{
if (obj is Country c)
{
if (Africa && c.Continent != Continent.Africa)
{
return false;
}
if (America && c.Continent != Continent.America)
{
return false;
}
}
return true;
}
}
public enum Continent
{
All,
Africa,
America
}
public class Country
{
public string DisplayName { get; set; }
public Continent Continent { get; set; }
}
}
我没有收到您遇到的绑定错误,但我的数据上下文是从 mvvmlight 创建的定位器中设置的。
如果您的代码在用户控件内使用,您可以通过获取网格并将其移动到用户控件内(没有数据上下文行)并使其派生自父数据上下文来更改此设置。
推荐阅读
- node.js - 我如何获取 url 字符串(节点,mongodb)
- ruby-on-rails - 无法在 Rails 中更新动作文本富文本
- java - AWS - 我的 Groovy Lambda 响应有什么问题?
- php - 在 bash 别名命令中,如何使 PHP 脚本的结果可用于 bash 脚本的其余部分?
- python - 如何将字典键附加到列表中?
- python - 在 python 中存储多处理队列的结果
- webgl - 对于批量渲染多个性能更高的相似对象,使用“退化三角形”的 drawArrays(TRIANGLE_STRIP) 还是 drawArraysInstanced?
- rust - 通过宏生成属性值
- c++ - 如何使用 CMake 自定义 Ninja 日志输出?(可能的?)
- lua - LUA - 为什么引用不需要结束?