项目描述:
MVVM模式下一个主页面,一个子窗口。
项目为小型项目。
场景描述:
数据库有两个表格:主页面表A,子窗口表B。
在主页面点击Dgv1选中一行数据(表A的某一行)
(页面传值过程)
点击一个按钮,打开子窗口。根据主页面中选中的Dgv1那一行数据的列的值①,查询子窗口表B的数据并显示。同时子窗口修改完并关闭子窗口后,主页面的页面数据会自动变化。
目前汇总:
按兔基大佬说法:打开新页面是View行为,不能完全局限在ViewModel。
群友大佬的说法:这场景看起来用viewfirst,虽然说有点落后。看起来不能用ViewModelFirst。
自己的想法:在主页面new一个子窗口view对象,通过构造函数传值(同事写winform是这样)
老大:用全局变量。
不知道还有啥方式
方法1示例
用了本质是全局变量的一种方式,在VM里new窗口了,破坏MVVM就破坏吧,反正都是我写。
ParamTransferService
cs
public class ParamTransferService
{
// 单例实例(线程安全)
private static readonly Lazy<ParamTransferService> _instance = new Lazy<ParamTransferService>(() => new ParamTransferService());
public static ParamTransferService Instance => _instance.Value;
// 私有构造函数
private ParamTransferService() { }
// 核心:用字典存储所有参数(键:参数唯一标识,值:参数值)
private readonly Dictionary<string, object> _params = new Dictionary<string, object>();
// 线程安全锁
private readonly object _lock = new object();
// --------------------------
// 兼容原有逻辑:保留图形引导界面的-ParamName的专用方法
// --------------------------
public void SetParamName(string paramName)
{
SetParam(ParamKeys.ConfGuideParamName, paramName); // 调用通用方法
}
public string GetParamName()
{
return GetParam<string>(ParamKeys.ConfGuideParamName); // 调用通用方法
}
// --------------------------
// 新增:通用参数设置/获取方法(支持任意参数)
// --------------------------
/// <summary>
/// 设置任意参数
/// </summary>
/// <param name="key">参数键(从ParamKeys获取,避免硬编码)</param>
/// <param name="value">参数值(任意类型)</param>
public void SetParam(string key, object value)
{
lock (_lock)
{
if (_params.ContainsKey(key))
_params[key] = value; // 覆盖已有值
else
_params.Add(key, value); // 新增值
}
}
/// <summary>
/// 获取任意参数(自动转换类型)
/// </summary>
/// <typeparam name="T">参数类型</typeparam>
/// <param name="key">参数键(从ParamKeys获取)</param>
/// <returns>参数值(默认值为default(T))</returns>
public T GetParam<T>(string key)
{
lock (_lock)
{
if (_params.TryGetValue(key, out object value) && value is T t)
return t;
return default; // 键不存在或类型不匹配时返回默认值
}
}
// 可选:删除指定参数(避免残留)
public void RemoveParam(string key)
{
lock (_lock)
{
_params.Remove(key);
}
}
}
ParamKyes
cs
public class ParamKeys
{
// --------------------------
// 图形引导页面ConfGuide-ParamName
// --------------------------
public const string ConfGuideParamName = "ParamName"; // 对应原来的ParamName
// 未来新增参数时,直接在这里添加常量即可
}
主页面VM按钮绑定方法
全局变量赋值为当前选中行
打开子窗口
cs
private void DoSelectedAndMark(object obj)
{
if (Dgv1SelectedRow == null)
{
//自定义弹窗,可自行替换
CustomMessageBoxView.ShowAsync("错误", "请先选择一个参数名称");
return;
}
//给全局变量赋值
ParamTransferService.Instance.SetParam(ParamKeys.ConfGuideParamName, Dgv1SelectedRow.ParamName);
//弹出子窗口
var editWindow = new EditImagePositionWindow();
var desktop = App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
editWindow.ShowDialog(desktop.MainWindow);
}
子窗口VM
获取主页面绑定的全局变量的值。
cs
#region 从主页面获取ParamName
private string _currentParamName;
#endregion
public EditImagePositionViewModel()
{
//通过全局单例服务获取主页面ParamName
_currentParamName = ParamTransferService.Instance.GetParamName();
CustomMessageBoxView.ShowAsync($"当前选中ParamName为:{_currentParamName}");
}
打开子页面后提示:

打开窗口的方式
cs
//弹出子窗口
var editWindow = new EditImagePositionWindow();
var desktop = App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
editWindow.ShowDialog(desktop.MainWindow);
解析:
1. 创建子窗口实例
var editWindow = new EditImagePositionWindow();
- 实例化
EditImagePositionWindow子窗口对象,执行其构造函数(初始化 UI、绑定 ViewModel 等,即你在EditImagePositionWindow构造函数中写的InitializeComponent()和DataContext = vm逻辑)。
2. 获取主窗口引用
var desktop = App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
-
App.Current.ApplicationLifetime获取当前应用的生命周期对象,由于是桌面应用,转换为IClassicDesktopStyleApplicationLifetime(Avalonia 桌面应用的标准生命周期接口)。 -
通过
desktop.MainWindow拿到应用的主窗口实例(通常是启动时设置的MainWindow),确保子窗口知道 "父窗口是谁"。
3. 以模态方式显示子窗口(核心效果)
editWindow.ShowDialog(desktop.MainWindow);
-
这是 Avalonia 中显示模态窗口的标准方式,核心效果是:
-
阻塞父窗口交互:子窗口打开后,主窗口会被 "冻结"(用户无法点击、输入或操作主窗口),必须先关闭子窗口(如通过你写的
InputElement_OnPointerPressed事件关闭),才能回到主窗口继续操作。 -
明确窗口层级:子窗口会以主窗口为 "所有者"(Owner),显示在主窗口上方,不会被主窗口遮挡,且关闭主窗口时会自动关闭子窗口(避免子窗口游离)。
-
等待子窗口关闭:
ShowDialog是异步方法(返回Task<object?>),虽然你的代码中没有await,但主窗口的当前操作流程会暂停,直到子窗口关闭后才继续(如果后续有代码的话)。
-