如何解决如何创建一个与另一个网格重叠的网格,并在第二个网格中显示第一个网格的某些控件
我正在使用文本框和标签创建多个网格。
有些网格共有一些文本框,而另一些文本框对于每个网格都是唯一的。
我在需要时使用可视性属性显示和折叠每个网格。 问题是,有没有办法在重叠的网格中使文本框从折叠的网格变为可见的可见网格?
也许有更好的控制方法可以做到这一点?
这是我要执行的操作的摘要示例:
XAML
<ToolBarTray>
<ToolBar>
<Button Name="showgrid1" Click="showgrid1_Click"/>
<Button Name="showgrid2" Click="showgrid2_Click"/>
</ToolBar>
</ToolBarTray>
<Grid Name="grid1" Visibility="Collapsed">
<TextBox Name="Common"/>
<TextBox Name="UniqueTogrid1"/>
</Grid>
<Grid Name="grid2" Visibility="Collapsed">
<TextBox Name="UniqueTogrid2"/>
</Grid>
C#背后的代码:
private void showgrid1_Click(object sender,RoutedEventArgs e)
{
grid1.Visibility = Visibility.Visible;
Common.Visibility = Visibility.Visible;
UniqueTogrid1.Visibility = Visibility.Visible;
grid2.Visibility = Visibility.Collapsed;
}
private void showgrid2_Click(object sender,RoutedEventArgs e)
{
grid1.Visibility = Visibility.Collapsed;
grid2.Visibility = Visibility.Visible;
UniqueTogrid2.Visibility = Visibility.Visible;
Common.Visibility = Visibility.Visible; //I want to show this textbox without declaring it in grid2 in XAML,while the grids are overlaping.
}
此代码不会在grid2中显示Common
文本框。
解决方法
即使进行编辑,问题也不是很清楚。特别是,您的代码示例未为所显示的UI元素提供任何实际的值,从而导致可见性的任何变化。如果没有数据,谁在乎什么时候可见?
也就是说,通过使用Click
事件作为响应用户输入的一种方式,我怀疑您类似地在某个地方有一些代码,可以为命名UI显式设置Text
属性您正在处理的元素。这导致您渴望重用<TextBox Name="Common"/>
元素,以便不重复代码。
如果这个推论是正确的,或者甚至接近正确,那么……动机是光荣的,但是由于不正确地使用WPF API,您已经陷入困境。具体来说,您应该将UI元素视为扔掉的对象,并将程序的所有有趣内容保留在称为“视图模型”的非UI对象中。请参阅“ MVVM”作为编程范例。
“丢弃”是指根据框架需要创建这些对象,以达到UI当前状态的目的。他们应该扮演的角色比这更重要。当我看一下您发布的代码示例时,代码中至少有两个主要的警告标志:元素具有名称,而背后的代码操纵着它们的视觉状态。
在编写良好的WPF代码中,几乎不需要这两个特性。 XAML通常可以完全不仅描述UI的外观,而且可以根据程序的操作如何改变视觉状态。
好吧,那么对于这种解释,如何实现代码以使其更适合WPF API,同时又使重复次数最少?
我至少可以立即看到几种方式。一种方法是基本上像现在一样保留XAML的安排,但是将重要元素移到适当的视图模型数据结构中。另一个方法是使用数据模板和多个视图模型数据结构根据当前活动的数据自动更新UI。我将在此处展示这两种方法。
方法1:
第一步是创建视图模型。恕我直言,WPF程序几乎总是从视图模型开始,因为存在XAML(用户界面)来为视图模型(程序数据)提供服务,而不是相反。理想情况下,视图模型应在UI框架上不具有 any 依赖性。它代表程序的状态,独立于特定于框架的事物,尽管它通常仍具有代表UI本身的条件方面的状态。
在某些情况下,您会发现您选择将视图模型用作更严格的 model 数据结构之间的适配器;这为程序增加了新的一层,从而允许模型数据结构完全独立于UI问题。在本例中,我并没有为此烦恼。
class ViewModel1 : NotifyPropertyChangedBase
{
private string _commonText = "default common text view model 1";
public string CommonText
{
get => _commonText;
set => _UpdateField(ref _commonText,value);
}
private string _uniqueText1 = "default unique text #1";
public string UniqueText1
{
get => _uniqueText1;
set => _UpdateField(ref _uniqueText1,value);
}
private string _uniqueText2 = "default unique text #2";
public string UniqueText2
{
get => _uniqueText2;
set => _UpdateField(ref _uniqueText2,value);
}
private int _gridToShow;
public int GridToShow
{
get => _gridToShow;
set => _UpdateField(ref _gridToShow,value);
}
public ICommand SetGridToShowCommand { get; }
public ViewModel1()
{
SetGridToShowCommand = new SetGridToShow(this);
}
private class SetGridToShow : ICommand
{
private readonly ViewModel1 _owner;
public SetGridToShow(ViewModel1 owner)
{
_owner = owner;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
if (parameter is int index ||
(parameter is string text && int.TryParse(text,out index)))
{
_owner.GridToShow = index;
}
}
}
}
此类具有WPF视图模型的一些典型功能:
- 它继承了一个基类,该基类完成了实现
INotifyPropertyChanged
的实际工作。 - 它具有表示程序当前状态的公共属性,并将用于XAML中声明的绑定中,以显示特定值或控制UI的特定状态。
- 它具有公共属性(在这种情况下为公共属性),命令可以对用户输入做出反应。在此特定示例中,单个命令的实现是一个独立的嵌套类,但是在实际程序中,通常也可以使用帮助程序类将其通用化,该帮助程序类执行诸如处理命令参数的类型转换和接受对象的委托等工作。实际执行命令。
在此示例中,视图模型包括三个string
属性,一个代表两个UI状态之间的共享值,然后另外两个,每个都是每个状态的“唯一”值,表示当前UI状态的int
属性和处理用户输入的ICommand
属性。
在声明了该视图模型之后,现在我们可以看看XAML:
<DockPanel>
<DockPanel.DataContext>
<l:ViewModel1/>
</DockPanel.DataContext>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Content="Show Grid 1" Command="{Binding SetGridToShowCommand}" CommandParameter="1"/>
<Button Content="Show Grid 2" Command="{Binding SetGridToShowCommand}" CommandParameter="2"/>
</ToolBar>
</ToolBarTray>
<Grid>
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding GridToShow}" Value="1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBox Text="{Binding CommonText}"/>
<TextBox Text="{Binding UniqueText1}"/>
</StackPanel>
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding GridToShow}" Value="2">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBox Text="{Binding CommonText}"/>
<TextBox Text="{Binding UniqueText2}"/>
</StackPanel>
</Grid>
</DockPanel>
以上与您的问题有关的重要部分是:
- 最重要的是,
CommonText
属性绑定到两个不同的TextBox
元素。即不共享XAML元素(这本来是您问题的字面答案),而是共享了基础视图模型属性。这样一来,UI可以以适合给定UI状态的任何方式与用户进行交互,而视图模型中只有一个状态可以表示用户的输入。 - 通过
DockPanel.DataContext
元素绑定,将视图模型对象设置为可视树这部分的数据上下文。 - 用户输入不是通过
Click
事件的处理程序来实现的,而是通过ICommand
根据输入来更新视图模型状态的。 - UI状态本身通过为每个“网格”设置的
DataTrigger
元素中提供的Style
元素来响应视图模型中的更改(我使用了StackPanel
而不是{ {1}},因为它更方便,但无论如何都适用相同的一般思想。)
方法2:
我认为仅此一个例子就足以解决您描述的情况。但是,WPF还可以通过数据模板的机制显示给定数据上下文对象的UI元素的完全不同的配置。如果我们将此想法应用于您的问题,我们可以:
- 再建立几个视图模型对象,以表示程序中的“唯一”值。
- 为每个视图模型对象声明一个模板。
- 不是使用
Grid
来更改UI的可视状态,而是让WPF只需更新当前显示的视图模型,即可通过模板自动更新状态。
在此方案中,这是我想出的视图模型对象……
主要的:
DataTrigger
和两个“独特”的:
class ViewModel2 : NotifyPropertyChangedBase
{
private readonly ViewModel2A _viewModel2A = new ViewModel2A();
private readonly ViewModel2B _viewModel2B = new ViewModel2B();
public string CommonText => "common text view model 2";
private object _gridViewModel;
public object GridViewModel
{
get => _gridViewModel;
set => _UpdateField(ref _gridViewModel,value);
}
public ICommand SetGridToShowCommand { get; }
public ViewModel2()
{
SetGridToShowCommand = new SetGridToShow(this);
}
private class SetGridToShow : ICommand
{
private readonly ViewModel2 _owner;
public SetGridToShow(ViewModel2 owner)
{
_owner = owner;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
if (parameter is int index ||
(parameter is string text && int.TryParse(text,out index)))
{
_owner.SetGridToShowIndex(index);
}
}
}
private void SetGridToShowIndex(int index)
{
GridViewModel = index == 1 ? (object)_viewModel2A : _viewModel2B;
}
}
在本示例中,我跳过了class ViewModel2A
{
public string UniqueText1 => "unique text grid #1";
}
class ViewModel2B
{
public string UniqueText2 => "unique text grid #2";
}
,只是使这些视图模型具有只读/仅显示属性。
请注意,在主视图模型中,当用户输入发生时,它所做的就是将当前的“网格”视图模型设置为适当的“唯一”视图模型对象。
有了这些,我们可以编写XAML:
INotifyPropertyChanged
在这里,不是使用触发器来设置样式,而是在父<DockPanel Grid.Column="1">
<DockPanel.DataContext>
<l:ViewModel2/>
</DockPanel.DataContext>
<DockPanel.Resources>
<DataTemplate DataType="{x:Type l:ViewModel2A}">
<StackPanel>
<!-- OneWay binding for illustration purposes (view model property is read-only) -->
<!-- RelativeSource allows for referencing properties from other than the current data context,such as the common text property -->
<TextBox Text="{Binding DataContext.CommonText,Mode=OneWay,RelativeSource={RelativeSource AncestorType=DockPanel}}"/>
<TextBox Text="{Binding UniqueText1,Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type l:ViewModel2B}">
<StackPanel>
<TextBox Text="{Binding DataContext.CommonText,RelativeSource={RelativeSource AncestorType=DockPanel}}"/>
<TextBox Text="{Binding UniqueText2,Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</DockPanel.Resources>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Content="Show Grid 1" Command="{Binding SetGridToShowCommand}" CommandParameter="1"/>
<Button Content="Show Grid 2" Command="{Binding SetGridToShowCommand}" CommandParameter="2"/>
</ToolBar>
</ToolBarTray>
<Grid>
<ContentControl Content="{Binding GridViewModel}"/>
</Grid>
</DockPanel>
元素的资源字典中声明了两个不同的模板,每种“唯一”视图模型类型都使用一个模板。然后,在DockPanel
控件中,将内容简单地绑定到当前的“唯一”视图模型对象。 WPF将根据当前“唯一”视图模型对象的类型选择正确的模板。
我在上面的XAML中做的一件稍微复杂的事情是将Grid
属性放在主视图模型中,从而使它实际上在两个视图状态中都通用。然后,这两个模板都使用CommonText
模式进行绑定来引用它。相反,也有可能让数据模板 only 为“唯一”属性提供UI元素,并让父UI元素处理RelativeSource
属性的显示。可以说这会更清洁,重复性更小,但与您最初发布的代码(我决定不越过那座桥)也有很大的不同。 :)
最后,以上所有内容都依赖于我之前提到的基类来实现CommonText
。有多种实现方法,但是为了完成上面的示例,以下是我用于上面代码的实现:
INotifyPropertyChanged
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。