Wpf MVVM动态绑定usercontrols并根据所选设计显示记录 [英] Wpf MVVM dynamic binding of usercontrols and show records as per selected design
问题描述
我需要在我的项目中提供帮助。不能得到任何正确的解决方案。我在这里解释一下我的场景。
我正在使用MVVM模式的WPF项目,我有主Windows屏幕,在主Windows屏幕加载时应该加载空白,只有一个标签。单击该标签时我必须打开新窗体,即窗口小部件屏幕,窗口小部件屏幕有多个按钮(数量为12),带有图像。
现在,点击任何按钮,我想要特定的按钮名称或参数,所以相应的名称或参数我必须从视图中找到用户控件并在主窗口屏幕上呈现用户控件与20记录的集合。如果我再次单击主窗口标签并从小部件屏幕中选择另一个按钮然后它应该加载另一个选定的用户控件将绑定ob主Windows屏幕。
所以,在根据窗口小部件屏幕中的选定设计,所有记录应使用用户控件在主窗口中显示。
这是我的主Windows屏幕代码,目前它是绑定静态用户控件,我直接提到控件来自12个用户控件其中一个是UserControlColumn1XL,它应该如何动态改变整个场景?
我尝试了什么:
< Window x:Class = TechMedic.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 温泉n>
xmlns:uc = clr-namespace:TechMedic.View.UserControls
xmlns:local = clr-namespace:TechMedic
xmlns:controls = clr-namespace:TechMedic.View.UserControls
xmlns:data = clr-namespace:TechMedic.Data
mc:Ignorable = d
WindowStartupLocation = CenterScreen Background = Black
Title = < span class =code-string> 主窗口} WindowState = < span class =code-string>最大化 MinWidth = 750 >
<的StackPanel>
< StackPanel Grid.Column = 0 Background = 黑色 VerticalAlignment = 顶部 >
< Border BorderThickness = 1 Background = Black BorderBrush = < span class =code-string> White >
< Button Style = {StaticResource TransparentStyle} Command = {Binding OnClickNormalCommand} CommandParameter = 正常前景= LightSkyBlue HorizontalAlignment = Center FontSize = 12pt >
< TextBlock Text = {Binding NormalCollection.Count,StringFormat = '正常:{0}'}填充= 0,5,0,5 Foreground = {Binding IsNormalColor} />
< / 按钮 >
< / Border >
< Border Name = UserControl1BorderNormal BorderBrush = 白色 BorderThickness = 0 >
< ItemsControl ItemsSource = {Binding NormalCollection} Name = ListNormal Margin = 4,0 >
< Items Control.ItemsPanel>
< ItemsPanelTemplate>
< WrapPanel HorizontalAlignment = Left />
< / ItemsPanelTemplate >
< / ItemsControl.ItemsPanel >
< ItemsControl.ItemTemplate>
< DataTemplate>
< controls:UserControlColumn1XL HorizontalAlignment = Left Margin = 2 />
< / DataTemplate >
< / ItemsControl.ItemTemplate >
< / ItemsControl >
< / 边框 >
< / StackPanel >
< / StackPanel >
< / 窗口 >
上面有多个问题。为了简单起见,我将尝试使用 MVVM设计模式 回答有关MainWindow导航的部分。
我确信有很多人对什么是最佳解决方案有不同的想法,并且有许多库基于 MVVM设计模式实现各种解决方案 使用依赖注入(DI)和控制反转(IOC)。
对于使用导航的解决方案,WPF具有Frame [ ^ ]控制和Page [ ^ ]视图。
如果您正在寻找更轻量级的东西,可以使用 ContentPresenter [ ^ ]控制到主机UserControl视图。这是我将在下面使用的解决方案。
因为我们使用 MVVM设计模式 ,我们无法直接从 ViewModel 引用控件。所以我们将使用 ViewModel 导航并依赖WPF的数据绑定和隐式模板来加载 UserControl 视图。
首先,我们需要为DataBinding添加一些核心机制来实现属性和命令Button事件。这些是 MVVM设计模式 的核心。
1.INotiftyPropertyChanged
public abstract class ObservableBase:INotifyPropertyChanged
{
public 无效设置< TValue>(参考 TValue字段,
TValue newValue,
[CallerMemberName] string propertyName = )
{
if (EqualityComparer< TValue> .Default.Equals(field, default (TValue))
||!field.Equals(newValue))
{
field = newValue;
PropertyChanged?.Invoke( this , new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged ;
}
2.ViewModelBase
public abstract class ViewModelBase:ObservableBase
{
public bool IsInDesignMode()
{
return Application.Current.MainWindow == null
? true
:DesignerProperties.GetIsInDesignMode(Application.Current.MainWindow);
}
}
3.ICommand
< pre lang =c#> public class RelayCommand< T> :ICommand
{
private readonly 行动< T>执行;
private readonly 谓词< T> canExecute;
public RelayCommand(Action< T> execute,Predicate< T> canExecute = null )
{
this .execute = execute ?? throw new ArgumentNullException( 执行跨度>);
this .canExecute = canExecute;
}
public bool CanExecute( object 参数)
= > canExecute == null || canExecute((T)参数);
public event EventHandler CanExecuteChanged
{
< span class =code-keyword> add = > CommandManager.RequerySuggested + = value ;
删除 = > CommandManager.RequerySuggested - = 值跨度>;
}
public void 执行( object 参数)
= > 执行(参数== null
?默认(T)
:(T)Convert.ChangeType(参数, typeof运算跨度>(T)));
}
接下来,我们需要设置页面。有两部分: ViewModel 和视图(UserControl)。保持 ViewModel 和查看具有相同前缀的IT总是很好的形式。
4.[第x页] ViewModels
public interface IPage
{
string 标题{获得跨度>; }
}
public abstract class PqgeViewModelBase:ViewModelBase,IPage
{
public PqgeViewModelBase( string title)
{
Title = title;
if (!IsInDesignMode())
{
// 此处仅运行时代码...
标题+ = 还活着!;
}
}
public string Title { get ; }
}
public class Page1ViewModel:PqgeViewModelBase
{
// 此处添加页面特定代码....
< span class =code-keyword> public Page1ViewModel(): base ( nameof ( Page1ViewModel))
{
}
}
public class Page2ViewModel:PqgeViewModelBase
{
// 在此处添加页面特定代码。 ...
public Page2ViewModel(): base ( nameof (Page2ViewModel))
{
}
}
5.[第x页]视图
< UserControl x:Class = WpfCPNav.Page1View
xmlns = http://schemas.microsoft.com / winfx / 2006 / xaml / presentation
xmlns:x = http://schemas.microsoft .com / winfx / 2006 / xaml
< span class =code-attribute> xmlns:mc = http://schemas.openxmlformats.org/markup-compatibility/ 2006
xmlns:d = http://schemas.microsoft.com/expression/blend/2008
mc:可忽略 = d d:DesignHeight = 450 d:DesignWidth = 800 >
< Viewbox >
< TextBlock 文字 = {Binding Title} / >
< / Viewbox >
< / UserControl >
< UserControl x:Class = WpfCPNav.Page2Viewxmlns = http://schemas.microsoft.com/winf x / 2006 / xaml / presentation
< span class =code-attribute>
xmlns:x = http://schemas.microsoft.com/winfx/2006/xaml
xmlns:mc = http://schemas.openxmlformats.org/markup-compatibility/2006
xmlns:d = http://schemas.microsoft.com/expression/blend/2008
mc:Ignorable < span class =code-keyword> = d d:DesignHeight = 450 d:DesignWidth = 800 >
< ; Viewbox >
< TextBlock 文字 = {Binding Title} / >
< / Viewbox >
< / UserControl >
现在我们有了这些页面,我们准备在应用程序的MainWindow
中托管它们。MainViewModel
将处理视图的切换。
6.MainViewModel
public class MainViewModel:ViewModelBase
{
private IPage内容;
public IPage内容{获取 = > 内容; 私有 设置 = > 设置(< span class =code-keyword> ref content, value ); }
public RelayCommand< int> NavigateCommand = > new RelayCommand< int>(导航);
private readonly 字典< int,Lazy< IPage>> pages
= new Dictionary< int,Lazy< IPage>>
{
[ 1 ] = new Lazy< IPage>(()= > new Page1ViewModel()),
[ 2 ] = new Lazy< IPage>(()= > new Page2ViewModel())
};
public MainViewModel()= > 导航( 1 跨度>);
private void 导航( int 值)= > 内容=页数[ value ].Value;
}
最后,我们需要做用户界面。当我们使用隐式模板时,我们在MainWindow
中定义它们。
7.MainWindow
< 窗口 x:Class = WpfCPNav.MainWindow
< span class =code-attribute> xmlns = http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x < span class =code-keyword> = http://schemas.microsoft.com/winfx/2006/xaml
xmlns:d = http:// schema s.microsoft.com/expression/blend/2008\"
xmlns:v=\"clr-namespace:WpfCPNav\"
xmlns:vm=\"clr-namespace:WpfCPNav\"
xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"
mc:Ignorable=\"d\" WindowStartupLocation=\"CenterScreen\"
Title=\"MainWindow\" Height=\"450\" Width=\"800\">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Dat aTemplate DataType=\"{x:Type vm:Page1ViewModel}\">
<v:Page1View />
</DataTemplate>
<DataTemplate DataType=\"{x:Type vm:Page2ViewModel}\">
<v:Page2View />
</DataTemplate>
<Style TargetType=\"Button\">
<Setter Property=\"Margin\" Value=\"10\"/>
<Setter Property=\"Padding\" Value=\"10 5\"/>
</Style>
<Style TargetType=\"StackPanel\">
<Setter Property=\"Orientation\" Value=\"Horizontal\"/>
<Setter Property=\"HorizontalAlignment\" Value=\"Center\"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=\"Auto\"/>
<RowDefinit ion/>
</Grid.RowDefinitions>
<StackPanel>
<Button Content=\"PAGE 1\" Command=\"{Binding NavigateCommand}\" CommandParameter=\"1\" />
<Button Content=\"PAGE 2\" Command=\"{Binding NavigateCommand}\" CommandParameter=\"2\" />
</StackPanel>
<ContentPres enter Content=\"{Binding Content}\" Grid.Row=\"1\"/>
</Grid>
</Window>
How it works:
When aButton
is clicked, theMainViewModel
will pass the selectedMainViewModel
to theContentPresenter
control, theContentPresenter
control will them look for a template and loads it diplaying the associated U serControl and the ViewModel is is automatically is automatically set to the UserControlDataContext
property and DataBinding is applied.
Your \"view\" is buried in a data template of an items control stuck in a stack panel with a bunch of second-fiddle controls.
You need to rethink your \"view architecture\" so that it is more \"manageable\" in terms of dynamic loading.
I need a help in my project.Could not get any proper solution as I want. I explain you my scenario here.
I am working on WPF project with MVVM pattern, where I have Main Windows screen, on load of Main Windows screen it should be load blank, and only one label is there. On Click of that Label I have to open new form that is Widget Screen and Widget Screen have multiple buttons(in number of 12) with Images.
Now, on click of any button, I want that particular button name or parameter, so accordingly that name or parameter I have to find User Controls from views and render User Controls on Main Windows screen with collection of 20 records. If again I click on Main Windows label and select another button from widget screen then it should load another selected User Controls will be bind ob Main Windows screen.
So, in short as per selected design from widget screen all records should be show in Main Windows using User Controls.
This is my Main Windows screen code, currently it is binding static User Control that I mentioned directly into controls from that 12 user controls one of them is UserControlColumn1XL, How it should be dynamically changed with whole scenario?
What I have tried:
<Window x:Class="TechMedic.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:uc ="clr-namespace:TechMedic.View.UserControls"
xmlns:local="clr-namespace:TechMedic"
xmlns:controls="clr-namespace:TechMedic.View.UserControls"
xmlns:data="clr-namespace:TechMedic.Data"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen" Background="Black"
Title="Main Window}" WindowState="Maximized" MinWidth="750" >
<StackPanel>
<StackPanel Grid.Column="0" Background="Black" VerticalAlignment="Top" >
<Border BorderThickness="1" Background="Black" BorderBrush="White">
<Button Style="{StaticResource TransparentStyle}" Command="{Binding OnClickNormalCommand}" CommandParameter="Normal" Foreground="LightSkyBlue" HorizontalAlignment="Center" FontSize="12pt" >
<TextBlock Text="{Binding NormalCollection.Count, StringFormat='Normal: {0}'}" Padding="0,5, 0, 5" Foreground="{Binding IsNormalColor}" />
</Button>
</Border>
<Border Name="UserControl1BorderNormal" BorderBrush="White" BorderThickness="0" >
<ItemsControl ItemsSource="{Binding NormalCollection}" Name="ListNormal" Margin="4,0" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserControlColumn1XL HorizontalAlignment="Left" Margin="2" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</StackPanel>
</StackPanel>
</Window>
There are multiple questions above. To keep it simple, I will try and answer the part about navigation in the MainWindow using the MVVM Design Pattern.
I'm sure that there are a lot of people have different ideas on what is the best solution and there are many libraries that implement a variety of solutions based on the MVVM Design Pattern using Dependency Injection (DI) and Inversion of Control (IOC).
For solutions that use navigation, WPF has the Frame[^] control and the Page[^] view.
If you are looking for something more lightweight, you can use ContentPresenter[^] control to host UserControl views. This is the solution that I'll use below.
Because we are using the MVVM Design Pattern, we cannot directly reference controls from the ViewModel. So we're going to use ViewModel navigation and rely on WPF's Data Binding and Implicit Templates do load the UserControl views.
First, we need to add some core mechanisms for DataBinding for properties and Commanding for the Button events. These are core to the MVVM Design Pattern.
1.INotiftyPropertyChanged
public abstract class ObservableBase : INotifyPropertyChanged { public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "") { if (EqualityComparer<TValue>.Default.Equals(field, default(TValue)) || !field.Equals(newValue)) { field = newValue; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public event PropertyChangedEventHandler PropertyChanged; }
2.ViewModelBase
public abstract class ViewModelBase : ObservableBase { public bool IsInDesignMode() { return Application.Current.MainWindow == null ? true : DesignerProperties.GetIsInDesignMode(Application.Current.MainWindow); } }
3.ICommand
public class RelayCommand<T> : ICommand { private readonly Action<T> execute; private readonly Predicate<T> canExecute; public RelayCommand(Action<T> execute, Predicate<T> canExecute = null) { this.execute = execute ?? throw new ArgumentNullException("execute"); this.canExecute = canExecute; } public bool CanExecute(object parameter) => canExecute == null || canExecute((T)parameter); public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } public void Execute(object parameter) => execute(parameter == null ? default(T) : (T)Convert.ChangeType(parameter, typeof(T))); }
Next, we need to set up our pages. There are two parts: ViewModel and the View (UserControl). IT is always good form to keep the ViewModel and the View with the same prefix.
4.[Page x]ViewModels
public interface IPage { string Title { get; } } public abstract class PqgeViewModelBase : ViewModelBase, IPage { public PqgeViewModelBase(string title) { Title = title; if (!IsInDesignMode()) { // runtime only code here... Title += " is alive!"; } } public string Title { get; } } public class Page1ViewModel : PqgeViewModelBase { // Add page specific code here.... public Page1ViewModel() : base(nameof(Page1ViewModel)) { } } public class Page2ViewModel : PqgeViewModelBase { // Add page specific code here.... public Page2ViewModel() : base(nameof(Page2ViewModel)) { } }
5.[Page x]Views
<UserControl x:Class="WpfCPNav.Page1View" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Viewbox> <TextBlock Text="{Binding Title}"/> </Viewbox> </UserControl>
<UserControl x:Class="WpfCPNav.Page2View" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Viewbox> <TextBlock Text="{Binding Title}"/> </Viewbox> </UserControl>
Now that we have the pages, we are ready to host them in theMainWindow
of the app. TheMainViewModel
will handle the switching of the views.
6.MainViewModel
public class MainViewModel : ViewModelBase { private IPage content; public IPage Content { get => content; private set => Set(ref content, value); } public RelayCommand<int> NavigateCommand => new RelayCommand<int>(Navigate); private readonly Dictionary<int, Lazy<IPage>> pages = new Dictionary<int, Lazy<IPage>> { [1] = new Lazy<IPage>(() => new Page1ViewModel()), [2] = new Lazy<IPage>(() => new Page2ViewModel()) }; public MainViewModel() => Navigate(1); private void Navigate(int value) => Content = pages[value].Value; }
Lastly, we need to do the User interface. As we are using Implicit templates, we define them in theMainWindow
.
7.MainWindow
<Window x:Class="WpfCPNav.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:v="clr-namespace:WpfCPNav" xmlns:vm="clr-namespace:WpfCPNav" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" WindowStartupLocation="CenterScreen" Title="MainWindow" Height="450" Width="800"> <Window.DataContext> <vm:MainViewModel/> </Window.DataContext> <Window.Resources> <DataTemplate DataType="{x:Type vm:Page1ViewModel}"> <v:Page1View /> </DataTemplate> <DataTemplate DataType="{x:Type vm:Page2ViewModel}"> <v:Page2View /> </DataTemplate> <Style TargetType="Button"> <Setter Property="Margin" Value="10"/> <Setter Property="Padding" Value="10 5"/> </Style> <Style TargetType="StackPanel"> <Setter Property="Orientation" Value="Horizontal"/> <Setter Property="HorizontalAlignment" Value="Center"/> </Style> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition/> </Grid.RowDefinitions> <StackPanel> <Button Content="PAGE 1" Command="{Binding NavigateCommand}" CommandParameter="1" /> <Button Content="PAGE 2" Command="{Binding NavigateCommand}" CommandParameter="2" /> </StackPanel> <ContentPresenter Content="{Binding Content}" Grid.Row="1"/> </Grid> </Window>
How it works:
When aButton
is clicked, theMainViewModel
will pass the selectedMainViewModel
to theContentPresenter
control, theContentPresenter
control will them look for a template and loads it diplaying the associated UserControl and the ViewModel is is automatically is automatically set to the UserControlDataContext
property and DataBinding is applied.
Your "view" is buried in a data template of an items control stuck in a stack panel with a bunch of second-fiddle controls.
You need to rethink your "view architecture" so that it is more "manageable" in terms of dynamic loading.
这篇关于Wpf MVVM动态绑定usercontrols并根据所选设计显示记录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!