WPF中在ScrollViewer内绑定动态创建控件(Catel MVVM迁移)
Hey there! Let's walk through how to migrate your dynamic XML-to-control logic from .NET 4.0/VS2010 to .NET 4.6.2 with Catel MVVM, specifically getting those controls loaded into a ScrollViewer when the view initializes.
Step 1: Set Up Your Catel View & ViewModel Structure
First, make sure your view inherits from Catel's Window (to leverage built-in MVVM features) and create a matching ViewModel.
View XAML (YourView.xaml)
We'll place a ScrollViewer and bind its content to a property in the ViewModel:
<catel:Window x:Class="YourApp.YourView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:catel="http://schemas.catelproject.com"> <Grid> <!-- Bind ScrollViewer content to our dynamic control property --> <ScrollViewer Content="{Binding DynamicContent}" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"/> </Grid> </catel:Window>
ViewModel (YourViewModel.cs)
Add a property to hold the dynamically generated root control, using Catel's built-in property change notification:
using Catel.MVVM; using System.Windows; public class YourViewModel : ViewModelBase { private UIElement _dynamicContent; // This property will hold our generated controls public UIElement DynamicContent { get => _dynamicContent; set => SetProperty(ref _dynamicContent, value); } }
Step 2: Migrate the XML-to-Control Generation Logic
You have two clean approaches here, depending on whether you want to keep view-related code in the view layer or use a service for reusability.
Option A: Generate Controls Directly in the View (Simple)
Since generating UI elements is view-specific, you can handle this in the view's code-behind when it loads:
using Catel.Windows; using System.IO; using System.Windows; using System.Windows.Markup; public partial class YourView : Catel.Windows.Window { public YourView() { InitializeComponent(); Loaded += OnViewLoaded; } private void OnViewLoaded(object sender, RoutedEventArgs e) { var viewModel = DataContext as YourViewModel; if (viewModel == null) return; // Replace with your XML file path or loading logic var xmlFilePath = "path/to/your/layout.xml"; viewModel.DynamicContent = GenerateControlsFromXml(xmlFilePath); } private UIElement GenerateControlsFromXml(string filePath) { // Reuse your existing XML parsing logic here var xmlContent = File.ReadAllText(filePath); return XamlReader.Parse(xmlContent) as UIElement; } }
Option B: Use a Service for Reusability (Clean MVVM)
If you want to reuse the XML generation logic across multiple views, create a service and inject it into the ViewModel:
- Create the Service Interface & Implementation
using System.Windows; public interface IXmlControlGenerator { UIElement GenerateFromFile(string xmlFilePath); } public class XmlControlGenerator : IXmlControlGenerator { public UIElement GenerateFromFile(string xmlFilePath) { // Your existing XML-to-control logic here var xmlContent = File.ReadAllText(xmlFilePath); return XamlReader.Parse(xmlContent) as UIElement; } }
- Register the Service in Catel's Container
In yourApp.xaml.cs:
using Catel.IoC; using System.Windows; public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { // Register the service with Catel's dependency injection var serviceLocator = ServiceLocator.Default; serviceLocator.RegisterType<IXmlControlGenerator, XmlControlGenerator>(); base.OnStartup(e); } }
- Call the Service from the ViewModel
Update your ViewModel to generate controls when it initializes:
using Catel.MVVM; using System.Threading.Tasks; public class YourViewModel : ViewModelBase { private readonly IXmlControlGenerator _controlGenerator; public UIElement DynamicContent { get; private set; } // Inject the service via constructor (Catel handles this) public YourViewModel(IXmlControlGenerator controlGenerator) { _controlGenerator = controlGenerator; } protected override async Task InitializeAsync() { await base.InitializeAsync(); // Generate controls when the ViewModel initializes DynamicContent = _controlGenerator.GenerateFromFile("path/to/your/layout.xml"); RaisePropertyChanged(nameof(DynamicContent)); } }
Key Notes to Avoid Pitfalls
- Data Context for Dynamic Controls: If your generated controls need to bind to ViewModel properties, set their
DataContextto the ViewModel when generating them (e.g.,rootElement.DataContext = this;in the service or view). - XAML Validity: Ensure your XML is valid WPF XAML (use
XamlReader.Parsesafely with try/catch blocks to handle parsing errors). - Catel Property Notifications: Use
SetPropertyorRaisePropertyChangedto ensure the view updates whenDynamicContentchanges.
内容的提问来源于stack exchange,提问作者user2086641

