gpt4 book ai didi

c# - WPF中的嵌套ObservableCollection数据绑定(bind)

转载 作者:太空宇宙 更新时间:2023-11-03 20:09:31 28 4
gpt4 key购买 nike

我是WPF的新手,并尝试使用WPF创建一个自学习应用程序。
我正在努力全面理解诸如数据绑定,数据模板,ItemControls之类的概念。

我正在尝试创建一个学习页面,并牢记以下要求。

1)该页面可以有多个问题。
问题填满了整个页面。
2)选择的格式因问题类型而异。
3)用户应能够选择问题的答案。

我遇到绑定嵌套ObservableCollection并显示上述要求的内容的问题。

有人可以帮助如何创建如下所示的页面以及如何在XMAL上使用INotifyPropertyChanged进行嵌套绑定。



这是我试图用来显示问题和答案的基本代码。

        namespace Learn
{
public enum QuestionType
{
OppositeMeanings,
LinkWords
//todo
}
public class Question
{
public Question()
{
Choices = new ObservableCollection<Choice>();

}
public string Name { set; get; }
public string Instruction { set; get; }
public string Clue { set; get; }
public ObservableCollection<Choice> Choices { set; get; }
public QuestionType Qtype { set; get; }
public Answer Ans { set; get; }
public int Marks { set; get; }
}
}

namespace Learn
{
public class Choice
{
public string Name { get; set; }
public bool isChecked { get; set; }
}
}
namespace Learn
{
public class NestedItemsViewModel
{
public NestedItemsViewModel()
{
Questions = new ObservableCollection<Question>();
for (int i = 0; i < 10; i++)
{
Question qn = new Question();

qn.Name = "Qn" + i;
for (int j = 0; j < 4; j++)
{
Choice ch = new Choice();
ch.Name = "Choice" + j;
qn.Choices.Add(ch);
}
Questions.Add(qn);
}

}

public ObservableCollection<Question> Questions { get; set; }
}

public partial class LearnPage : UserControl
{

public LearnPage()
{
InitializeComponent();

this.DataContext = new NestedItemsViewModel();

}

}
}

最佳答案

您最初的尝试可以使您达到目标的80%。希望我的回答能使您更加接近。

首先,INotifyPropertyChanged是对象支持的接口,用于通知Xaml引擎数据已被修改,并且需要更新用户界面以显示更改。您只需要对标准clr属性执行此操作。

因此,如果您的数据流量是从ui到模型的全部一种方式,则无需实现INotifyPropertyChanged。

我创建了一个使用您提供的代码的示例,我对其进行了修改并创建了一个视图来显示它。 ViewModel和数据类如下
    公共枚举QuestionType
    {
        相反,
        链接词
    }

public class Instruction
{
public string Name { get; set; }
public ObservableCollection<Question> Questions { get; set; }
}

public class Question : INotifyPropertyChanged
{
private Choice selectedChoice;
private string instruction;

public Question()
{
Choices = new ObservableCollection<Choice>();

}
public string Name { set; get; }
public bool IsInstruction { get { return !string.IsNullOrEmpty(Instruction); } }
public string Instruction
{
get { return instruction; }
set
{
if (value != instruction)
{
instruction = value;
OnPropertyChanged();
OnPropertyChanged("IsInstruction");
}
}
}
public string Clue { set; get; }
public ObservableCollection<Choice> Choices { set; get; }
public QuestionType Qtype { set; get; }

public Choice SelectedChoice
{
get { return selectedChoice; }
set
{
if (value != selectedChoice)
{
selectedChoice = value;
OnPropertyChanged();
}
}
}
public int Marks { set; get; }

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

public class Choice
{
public string Name { get; set; }
public bool IsCorrect { get; set; }
}

public class NestedItemsViewModel
{
public NestedItemsViewModel()
{
Questions = new ObservableCollection<Question>();
for (var h = 0; h <= 1; h++)
{
Questions.Add(new Question() { Instruction = string.Format("Instruction {0}", h) });
for (int i = 1; i < 5; i++)
{
Question qn = new Question() { Name = "Qn" + ((4 * h) + i) };
for (int j = 0; j < 4; j++)
{
qn.Choices.Add(new Choice() { Name = "Choice" + j, IsCorrect = j == i - 1 });
}
Questions.Add(qn);
}
}
}

public ObservableCollection<Question> Questions { get; set; }

internal void SelectChoice(int questionIndex, int choiceIndex)
{
var question = this.Questions[questionIndex];
question.SelectedChoice = question.Choices[choiceIndex];
}
}


请注意,答案已更改为SelectedChoice。这可能不是您所需要的,但是它使示例变得容易一些。我还对SelectedChoice实现了INotifyPropertyChanged模式,因此我可以从代码(特别是从对SelectChoice的调用)中设置SelectedChoice。

后面的主要Windows代码实例化ViewModel并处理一个按钮事件,以从后面的代码中进行选择(以显示INotifyPropertyChanged的工作方式)。

public partial class MainWindow : Window
{
public MainWindow()
{
ViewModel = new NestedItemsViewModel();
InitializeComponent();
}

public NestedItemsViewModel ViewModel { get; set; }

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
ViewModel.SelectChoice(3, 3);
}
}


Xaml是

<Window x:Class="StackOverflow._20984156.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:learn="clr-namespace:StackOverflow._20984156"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>

<learn:SelectedItemIsCorrectToBooleanConverter x:Key="SelectedCheckedToBoolean" />

<Style x:Key="ChoiceRadioButtonStyle" TargetType="{x:Type RadioButton}" BasedOn="{StaticResource {x:Type RadioButton}}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource SelectedCheckedToBoolean}">
<Binding Path="IsCorrect" />
<Binding RelativeSource="{RelativeSource Self}" Path="IsChecked" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource SelectedCheckedToBoolean}">
<Binding Path="IsCorrect" />
<Binding RelativeSource="{RelativeSource Self}" Path="IsChecked" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>

<DataTemplate x:Key="InstructionTemplate" DataType="{x:Type learn:Question}">
<TextBlock Text="{Binding Path=Instruction}" />
</DataTemplate>

<DataTemplate x:Key="QuestionTemplate" DataType="{x:Type learn:Question}">
<StackPanel Margin="10 0">
<TextBlock Text="{Binding Path=Name}" />
<ListBox ItemsSource="{Binding Path=Choices}" SelectedItem="{Binding Path=SelectedChoice}" HorizontalAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type learn:Choice}">
<RadioButton Content="{Binding Path=Name}" IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Margin="10 1"
Style="{StaticResource ChoiceRadioButtonStyle}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</Window.Resources>

<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom">
<Button Content="Select Question 3 choice 3" Click="ButtonBase_OnClick" />
</StackPanel>
<ItemsControl ItemsSource="{Binding Path=Questions}">
<ItemsControl.ItemTemplateSelector>
<learn:QuestionTemplateSelector QuestionTemplate="{StaticResource QuestionTemplate}" InstructionTemplate="{StaticResource InstructionTemplate}" />
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
</DockPanel>
</Window>


注意:我的学习命名空间与您的学习命名空间不同,因此,如果您使用此代码,则需要将其修改为您的命名空间。

因此,主ListBox显示一个问题列表。 ListBox中的每个项目(每个问题)均使用DataTemplate呈现。同样,在DataTemplate中,一个ListBox用于显示选择,而DataTemplate用于将每个选择呈现为单选按钮。

兴趣点。


每个选择都绑定到它所属的ListBoxItem的IsSelected属性。它可能不会出现在xaml中,但每个选择都会有一个ListBoxItem。 IsSelected属性与ListBox的SelectedItem属性保持同步(通过ListBox),并且该属性绑定到问题中的SelectedChoice。
选择列表框具有一个ItemsPanel。这使您可以使用其他类型的面板的布局策略来布局ListBox的项目。在这种情况下,将使用水平StackPanel。
我添加了一个按钮,用于在视图模型中将问题3的选择设置为3。这将向您显示INotifyPropertyChanged的工作。如果从SelectedChoice属性的设置器中删除OnPropertyChanged调用,则该视图将不反映更改。


上面的示例不处理指令类型。

为了处理指令,我要么


将指令作为问题插入并更改问题DataTemplate,以使其不显示指令的选择。要么
在视图模型中创建指令的集合,其中指令类型具有问题的集合(视图模型将不再具有问题的集合)。


指令类将类似于

public class Instruction
{
public string Name { get; set; }
public ObservableCollection<Question> Questions { get; set; }
}


基于有关计时器到期和多页注释的添加。

此处的注释旨在为您提供足够的信息以了解要搜索的内容。

INotifyPropertyChanged

如有疑问,请实现INotifyPropertyChanged。我上面的评论是让您知道为什么使用它。如果已经显示了可以通过代码进行处理的数据,则必须实现INotifyPropertyChanged。

ObservableCollection对象非常适合处理代码中的列表操作。它不仅实现INotifyPropertyChanged,而且还实现INotifyCollectionChanged,这两个接口都确保如果集合发生更改,则xaml引擎会知道它并显示更改。请注意,如果您修改集合中对象的属性,则可以通过在对象上实现INotifyPropertyChanged来将更改通知Xaml引擎。 ObservableCollection非常棒,而不是万能的。

分页

对于您的方案,分页很简单。将问题的完整列表存储在某个位置(内存,数据库,文件)。当您转到第1页时,向商店查询这些问题,然后用这些问题填充ObservableCollection。当您转到第2页时,向商店查询第2页的问题,请清除ObservableCollection并重新填充。如果您一次实例化ObservableCollection,然后在分页时清除并重新填充它,则将为您处理ListBox刷新。

计时器

从Windows的角度来看,计时器会占用大量资源,因此应谨慎使用。您可以使用.net中的许多计时器。我倾向于玩 System.Threading.TimerSystem.Timers.Timer。两者都在DispatcherThread之外的线程上调用计时器回调,这使您可以在不影响UI响应能力的情况下进行工作。但是,如果在工作期间需要修改UI,则需要 Dispatcher.InvokeDispatcher.BeginInvoke才能返回Dispatcher线程。 BeginInvoke是异步的,因此,在等待DispatcherThread变为空闲时,不应挂起线程。

根据有关数据模板分离的注释进行添加。

我向Question对象添加了一个IsInstruction(我没有实现Instruction类)。这显示了一个从属性A(指令)引发属性B(IsInstruction)引发PropertyChanged事件的示例。

我将DataTemplate从列表框中移到Window.Resources并给了它一个密钥。我还为指令项目创建了第二个DataTemplate。

我创建了一个 DataTemplateSelector来选择要使用的DataTemplate。当需要在加载数据时选择一个DataTemplate时,DataTemplateSelector很好。认为它是OneTime选择器。如果您需要DataTemplate在其呈现的数据范围内进行更改,则应使用触发器。选择器的代码是

public class QuestionTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
DataTemplate template = null;

var question = item as Question;
if (question != null)
{
template = question.IsInstruction ? InstructionTemplate : QuestionTemplate;
if (template == null)
{
template = base.SelectTemplate(item, container);
}
}
else
{
template = base.SelectTemplate(item, container);
}

return template;
}

public DataTemplate QuestionTemplate { get; set; }

public DataTemplate InstructionTemplate { get; set; }
}


选择器绑定到ItemsControl的ItemTemplateSelector。

最后,我将ListBox转换为ItemsControl。 ItemsControl具有ListBox的大多数功能(ListBox控件派生自ItemsControl),但缺少Selected功能。这将使您的问题看起来更像是问题页面而不是列表。

注意:尽管我仅将DataTemplateSelector的代码添加到了添加内容中,但我在其余的答案中更新了代码段,以使用新的DataTemplateSelector。

根据关于设置正确和错误答案的背景的注释进行添加

根据模型中的值动态设置背景需要一个触发器,在这种情况下,需要多个触发器。

我已经更新了Choice对象,使其包含IsCorrect,并且在ViewModel中创建问题期间,我为每个答案在Choices中分配了IsCorrect。

我还更新了MainWindow,以在RadioButton上包含样式触发器。关于触发器,有几点要注意
1.鼠标悬停时,样式或RadioButton设置背景。要解决此问题,需要重新创建RadioButton的样式。
1.由于触发器基于2个值,因此我们可以在模型上创建另一个属性以组合这2个属性,也可以使用MultiBinding和MultValueConverter。我使用了 MultiBinding,而 MultiValueConverter如下。

public class SelectedItemIsCorrectToBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var boolValues = values.OfType<bool>().ToList();

var isCorrectValue = boolValues[0];
var isSelected = boolValues[1];

if (isSelected)
{
return isCorrectValue;
}

return DependencyProperty.UnsetValue;
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}


我希望这有帮助

关于c# - WPF中的嵌套ObservableCollection数据绑定(bind),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20984156/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com