C# WPF -从Window向页面的ViewModel传递IConfiguration

wsewodh2  于 2023-05-01  发布在  C#
关注(0)|答案(2)|浏览(190)

我可以很容易地传入一个IConfiguration应用程序设置。json到WPF中的一个窗口,这是我过去所做的。现在,我尝试以正确的方式完成此操作,并将其传递到ViewModel中,该ViewModel被分配为正在Window上显示的Page的DataContext。我正在把页面加载到窗口的框架中。我想把配置传递给页面的ViewModel,这样我就不用手动重读了。
以下是我目前所做的和我尝试过的一些事情:

public partial class HomeWindow : Window
    {
        readonly IConfiguration _config;
        
        public HomeWindow(IConfiguration config)
        {
            _config = config;

            // Instantiate the Object with JSON settings.
            InitializeComponent();
            DataContext = new HomeWindowVM(config)
        } 
    }

    public class HomeWindowVM
    {
        readonly IConfiguration _config;
        //public INotify btnCheckoutPage { get; set; }
        public object MyFrameContent { get; set; }

        public HomeWindowVM(IConfiguration config)
        {
            _config = config;
            CheckoutClickCommand = new MyCommand(CheckoutClick);
            ShipmentClickCommand = new MyCommand(ShipmentClick);
            frameURI = new INotify();
            frameVis = new INotify();
            frameVis.MyProperty = "Hidden";
        }

        private void CheckoutClick()
        {
            frameURI.MyProperty = "CheckoutPage.xaml";
            frameVis.MyProperty = "Visible";
// I tried this 
            //CheckoutPage checkoutPage = new CheckoutPage(_config);
            //MyFrameContent = new CheckoutPage(_config);

        }
    }

    public partial class CheckoutPage : Page
    {
        readonly IConfiguration _config;

        public CheckoutPage()
        {
            InitializeComponent();
            DataContext = new CheckoutPageVM();
        }
        public CheckoutPage(IConfiguration config)
        {
            _config = config;
            InitializeComponent();

            //Frame frame = new Frame();
            //MainView 
            DataContext = new CheckoutPageVM(config);
        }
    }

    public class CheckoutPageVM 
    {
        readonly IConfiguration _config;

        public CheckoutPageVM(IConfiguration config)
        {
            _config = config;
            LblErrorProjectNum = new INotify();
            LblErrorProjectNum.MyProperty = "Hidden";
            LblErrorTransfer = new INotify();
            LblErrorTransfer.MyProperty = "Hidden";
            CheckoutSubmitCommand = new MyCommand(CheckoutSubmit);            
        }
relj7zay

relj7zay1#

没有一个正确的方法来做到这一点,但我猜你有ASP的背景。NET,熟悉依赖注入。当然,WPF不使用这种模式,在这种情况下,它可能是矫枉过正。静态可访问的单例工作得很好,避免了通过构造函数或其他过度设计的解决方案传递配置示例的困境:

// In your Model layer
public static class MyConfiguration
{
     public static IConfiguration Instance { get; private set; }

     public static void Load(IConfiguration config) 
     {
          // call this when the app initializes
          Instance = config;
     }
}

然后,如果单个类需要示例属性进行绑定,等等。,就像这样简单:

public partial class HomeWindow : Window
{
    public IConfiguration Config => MyConfiguration.Instance;
    // ...
}

public class CheckoutPageVM 
{
    public IConfiguration Config => MyConfiguration.Instance;
    // ...
}

public partial class CheckoutPage : Page
{
    public IConfiguration Config => MyConfiguration.Instance;
    // ...
}

等等

0tdrvxhp

0tdrvxhp2#

首先,您的视图不应该依赖于任何应用程序设置模型。因此,WindowPage示例对MVVM应用程序中IConfiguration对象的依赖性是错误的。配置是应用程序 * 型号 * 的一部分。
为了避免这种依赖性,最好的方法是使用所谓的 View-Model-First 模式。该模式要求您的 View Model 类决定要加载哪个 View 类。这与当前的 * View-First * 模式相反,在该模式中,View 决定加载哪个 View Model

  • View-Model-First* 通常通过将 View Model 类绑定到 View(例如ContentPresenterContentControl或派生类)来实现。然后定义一个DataTemplate,它将由框架基于当前数据类型(视图模型类型)加载和呈现。此DataTemplate包含您要显示的实际视图。

这消除了 ViewModel 类的依赖性(在本例中,这是通过允许视图显式地构造其 View Model 类而引入的)。
您可以查看以下示例以了解 View-Model-FirstC# WPF Navigation Between Pages (Views)
出于这个原因,我建议改变您的方法,实现MVVM友好的 View-Model-First 模式。
即使您没有实现MVVM模式,您也将获得一个更易于维护和扩展的应用程序。
无论使用 View-First 还是 View-Model-FirstIConfiguration示例都必须来自应用程序 Model
当前代码的第一个修正或改进是将IConfiguration示例化移动到 ModelView Model 必须从 Model 而不是 View 获取此类应用程序数据。为此,示例创建了一个ConfigurationRepositoty类,该类封装了IConfiguration示例的创建。由于数据持久性也是 Model 的一部分,因此ConfigurationRepositoty可以轻松访问文件或数据库等资源。理想情况下,它将使用其他 Model 类,这些类详细了解如何获取数据(从文件或数据库读取)。
ConfigurationRepositoty要么直接在 View Model 类中创建,要么通过构造函数注入(在实现任何IoC的情况下,如 Dependency Injection 模式)。
关于视图模型构造函数依赖项,您应该引入一个工厂,其中视图模型具有构造函数依赖项。考虑实现 Abstract Factory 模式。View-First 实现的结果是 View 显式地示例化视图,因此必须知道所有的依赖关系才能满足构造函数。
由于 View Model 类的依赖项通常是 Model 类,因此在 View 中声明的依赖项是非法的或在设计方面有缺陷。当使用工厂(或抽象工厂)时,这些依赖性被消除。
第二个强制修复是将页面导航从 View Model 移动到 View
因为您实现了 View-First 导航,所以您可以通过创建视图的示例或通过URI显式地知道它们来导航。
但是您的 View Model 不能知道/依赖于 View 类型或它们的URI。这使它们不具备充当导航控制器的资格。
如果您想从 View Model 导航,那么就实现 View-Model-First 模式。
最后,MyFrameContent属性看起来也非常可疑。根据名称,似乎所有者视图模型类对视图有太多的了解,甚至可能是具体的依赖关系。

配置存储库。cs

一个 Model 类,知道如何获取或创建IConfiguration示例。

public class ConmfigurationRepository
{
  private static Lazy<IConfiguration> SharedConfiguration { get; } 
    = new Lazy<IConfiguration>(InitializeSharedConfiguration);

  public IConfiguration GetApplicationConfiguration() 
    => this.SharedConfiguration;

  // In case there are different implementations of IConfiguration
  // for example when there are different configuration contexts
  // then expose a related API
  public IConfiguration GetHomePageConfiguration()
  {
    // TODO::Create or get a new/shared IConfiguration instance.
    // If the configuration comes from an external source like a file or database,
    // then this is the place to read it (using related classes or services)
  }

  private static IConfiguration InitializeSharedConfiguration()
  {
    // TODO::Create or get a new/shared IConfiguration instance.
    // If the configuration comes from an external source like a file or database,
    // then this is the place to read it (using related classes or services)
  }
}

主窗口。xaml.cs

partial class MainWindow : Window
{
  private void ShowView(string viewName)
  {
    Page destinationPage = viewName switch
    {
      nameof(HomePage) => CreateHomeView(),
      nameof(CheckoutPage) => CreateCheckoutView(),
      _ => throw new ArgumentException("View not found", nameof(viewName)),
    };

    // Show view
    this.frame.Content = destinationPage;
  }

  private Page CreateHomeView()
  {
    // If HomePageVM has constructor dependencies 
    // use a factory to instantiate the type
    HomePageVM homePageVm = new HomePageVM();
    var homeView = new HomeView(homePageVm);
    homeView.NavigationRequested += OnNavigationRequested;

    return homeView;
  }

  private Page CreateCheckoutView()
  {
    // If CheckoutPageVM has constructor dependencies 
    // use a factory to instantiate the type
    CheckoutPageVM checkoutPageVm = new CheckoutPageVM();

    return new CheckoutPage(checkoutPageVm);
  }

  private void OnNavigationRequested(object sender, NavigationRequestedEventArgs e)
  {
    var navigationSource = (INavigationSource)sender;
    navigationSource.NavigationRequested -= OnNavigationRequested;
    ShowView(e.DestinationViewName);
  }
}

首页.cs

public partial class HomePage : Page, INavigationSource
{        
  public HomeWindow(HomePageVM homePageVM)
  { 
    InitializeComponent();

    homePageVM.CheckedOut += OnCheckedOut;
    this.DataContext = homePageVM;
  } 

  private void OnCheckedOut(object sender, EventArgs e)
  {
    ((HomePageVM)this.DataContext).CheckedOut -= OnCheckedOut;
    OnNavigationRequested(nameof(CheckoutPage));
  }

  private void OnNavigationRequested(string destinationViewName)
    => this.NavigationRequested?.Invoke(this, new NavigationRequestedEventArgs(destinationViewName));
}

CheckoutPage.cs

public partial class CheckoutPage : Page, INavigationSource
{        
  public CheckoutPage(CheckoutPageVM checkoutPageVM)
  { 
    InitializeComponent();

    this.DataContext = checkoutPageVM;
  } 
}

INavigationSource。cs

由需要导航离开的所有页面实现。

public interface INavigationSource
{
  public event EventHandler<NavigationRequestedEventArgs> NavigationRequested;
}

NavigationRequestedEventArgs。cs

public class NavigationRequestedEventArgs : EventArgs
{
  public DestinationViewName DestinationViewName { get; }

  public NavigationRequestedEventArgs(string destinationViewName)
  {
    this.DestinationViewName = destinationViewName;
  }
}

HomePageVM。cs

public class HomePageVM
{
  readonly IConfiguration _config;

  public HomePageVM()
  {
    _config = new ConmfigurationRepository().GetApplicationConfiguration();
    CheckoutClickCommand = new MyCommand(ExecuteCheckoutCommand);
    ShipmentClickCommand = new MyCommand(ShipmentClick);  
  }

  private void ExecuteCheckoutCommand()
    => OnCheckedOut();

  private void OnCheckedOut()
    => this.CheckedOut?.Invoke(this, EventArgs.Empty);
}

CheckoutPageVM。cs

public class CheckoutPageVM
{
  readonly IConfiguration _config;

  public CheckoutPageVM()
  {
    _config = new ConmfigurationRepository().GetApplicationConfiguration();
  }
}

相关问题