Wpf MVVM动态绑定usercontrols并根据所选设计显示记录 [英] Wpf MVVM dynamic binding of usercontrols and show records as per selected design

查看:71
本文介绍了Wpf MVVM动态绑定usercontrols并根据所选设计显示记录的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在我的项目中提供帮助。不能得到任何正确的解决方案。我在这里解释一下我的场景。



我正在使用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
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 Horizo​​ntalAlignment = 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 Horizo​​ntalAlignment = Left />
< / ItemsPanelTemplate >
< / ItemsControl.ItemsPanel >
< ItemsControl.ItemTemplate>
< DataTemplate>
< controls:UserControlColumn1XL Horizo​​ntalAlignment = 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.Page2View  

< span class =code-attribute> xmlns = http://schemas.microsoft.com/winf x / 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 < 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 a Button is clicked, the MainViewModel will pass the selected MainViewModel to the ContentPresenter control, the ContentPresenter 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 UserControl DataContext 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 the MainWindow of the app. The MainViewModel 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 the MainWindow.

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 a Button is clicked, the MainViewModel will pass the selected MainViewModel to the ContentPresenter control, the ContentPresenter 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 UserControl DataContext 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆