如何通过代码调用构造函数将Page作为内容放入ScrollViewer等控件
问题
是否可以通过代码调用构造函数的方式,将Page作为内容放入<Grid/>、<StackPanel/>、<ContentControl/>或<ScrollViewer/>控件中?
期望实现的效果如下:
XAML:
<Grid> <ScrollViewer Content="{Binding Panel0}"/> </Grid>
C#:
public class TestWindowViewModel : Page { public string Name { get; private set; } public string Description { get; private set; } public TestWindowViewModel(string name, string description) { Name = name; Description = description; } }
public partial class SomeViewModel : Page { public TestWindowViewModel Panel0; public SomeViewModel() { Panel0 = new TestWindowViewModel("panelName", "panelDescription"); InitializeComponent(); } }
回答
可以实现,但你当前的写法有几个明显问题,会导致功能无法正常运行:
- WPF绑定系统仅识别公共属性,你定义的
Panel0是公共字段,绑定根本无法获取到值,必须改成公共属性。 - 分层逻辑错误:
Page属于视图层UI元素,ViewModel是承载数据和业务逻辑的层,不应该继承Page这类UI基类,更不应该在ViewModel里直接持有UI元素实例。 - WPF的UI元素遵循“单父节点”规则,同一个
Page实例不能同时挂载到多个可视化树的节点上,否则会直接抛出异常。
修正方案
方案1:直接后台代码创建赋值(最直接,匹配“调用构造函数创建Page放入控件”的需求)
不需要写绑定,直接在父视图的后台代码创建Page实例赋值给对应容器即可:
- 父视图XAML,给目标控件命名:
<Grid> <ScrollViewer x:Name="PageContainer"/> </Grid> - 父视图后台代码(注意是视图的.cs文件,不是ViewModel):
public partial class SomePage : Page { public SomePage() { InitializeComponent(); // 调用构造函数创建子Page实例 var childPage = new TestPage(); // 给子Page设置数据上下文,绑定业务数据 childPage.DataContext = new TestPageViewModel("panelName", "panelDescription"); // 直接赋值给ScrollViewer的Content PageContainer.Content = childPage; } } - 业务ViewModel类,移除对Page的继承:
// ViewModel是纯逻辑类,不继承任何UI基类 public class TestPageViewModel { public string Name { get; private set; } public string Description { get; private set; } public TestPageViewModel(string name, string description) { Name = name; Description = description; } }
方案2:保留绑定写法
如果要沿用原来的绑定写法,需要做两个修改:
- 把
Panel0从字段改成公共属性 - 给父视图设置正确的DataContext,且
Panel0的返回值是Page类型的实例
但这种写法ViewModel会直接持有UI元素,严重违反MVVM分层原则,不推荐在正式项目使用。
推荐实践
不要在ViewModel里直接创建/持有Page实例,用DataTemplate关联ViewModel和对应的视图,让WPF框架自动完成视图的创建和挂载,既符合分层规范,也不用手动处理UI元素的生命周期。
内容的提问来源于stack exchange,提问作者Cobret
相关产品推荐
相关产品推荐

