程式資訊
- 程式 ID:
ContentWindow - 所屬子系統:Other
- 狀態:stable
內容視窗容器(ContentWindow)¶
本頁屬系統機制說明
「內容視窗容器」不是業務表單,沒有自己的查詢、欄位或單據。它是整個系統各種模態彈窗(轉單、設定、子功能對話框、進度條、附件、簽核…)共用的外框:負責標題列、視窗大小、關閉按鈕、確定/取消(DialogResult)與生命週期。本頁說明這個框架如何運作,受眾偏向系統與開發人員;一般使用者多半不會直接看到「ContentWindow」這個名字,而是看到它承載的各種彈窗內容。
功能簡介¶
當系統需要以模態(彈出後必須處理完才能回主畫面)方式顯示一段內容時,不會各自 new 一個獨立 Window,而是統一透過 Context.ContentWindowService.ShowContentWindow(vm) 把一個「內容 ViewModel」交給 ContentWindow 承載。ContentWindow 提供共用的外框:
- 一條標題列(顯示內容 VM 給的標題)。
- 視窗大小/置中(預設置於主視窗中央,可由內容 VM 指定寬高或自動貼合內容)。
- 一個
ContentControl,把內容 VM 透過App.xaml的 DataTemplate 對應到實際畫面塞進去。 - 關閉按鈕的顯示/隱藏,以及
確定/取消的結果回傳(DialogResult)。
在流程中的位置:它是 MVVM 模態對話框的「承載外框」。任何需要彈窗的功能(例如轉傳票、簽核、附件、報表預覽、進度條等)都共用這一個容器,因此外觀與行為一致。
另有一個無邊框的變體 NoBorderContentWindow,用於不需要標題列/邊框的浮動內容(例如全透明的進度浮層)。
前置需求¶
| 條件 | 影響 |
|---|---|
內容 VM 實作 IContentOfContentWindow |
唯有實作此介面(一般繼承 ERPProgramContentWindowViewModel)的 VM 才能被 ShowContentWindow 承載 |
內容 VM 覆寫 Parameter |
提供標題、寬、高、是否顯示關閉按鈕、是否貼合內容;缺少則用容器預設值 |
App.xaml 有對應的 DataTemplate |
容器只放 ContentControl,實際畫面靠 App.xaml 把內容 VM 型別對應到 View;少了對應會顯示空白 |
| 主視窗(MainWindow)已開啟 | 容器以主視窗為 Owner 並置中;主視窗的寬高也決定彈窗的預設最大尺寸(主視窗寬高各減 100) |
沒有獨立權限
內容視窗容器本身不掛任何選單代碼或權限。能不能開某個彈窗,取決於承載在裡面的那支業務功能的權限,與容器無關。
操作步驟¶
對一般使用者而言,操作的是「彈窗裡的內容」,容器只提供共通的關閉行為:
- 從某支業務功能觸發彈窗(例如按工具列的轉傳票、簽核、附件等)。系統會以模態方式彈出
ContentWindow,主畫面在彈窗關閉前會被鎖住(無法操作背後的版面)。 - 在彈窗標題列確認標題文字,了解這個彈窗在做什麼。
- 操作彈窗內的內容(依各功能而異)。
- 結束方式有兩種:
- 完成並確認:內容功能會把結果設為「成功」(
DialogResult = true),彈窗關閉,呼叫端視為使用者按了確定。 - 取消/關閉:按彈窗的關閉按鈕或內容功能提供的
取消(對應DialogResult = false),彈窗關閉,呼叫端視為使用者放棄。
- 完成並確認:內容功能會把結果設為「成功」(
- 彈窗關閉後,主畫面自動解除鎖定,回到原本的作業。
關閉按鈕可被隱藏
某些「不可中途中斷」的彈窗(例如執行中的進度條)會隱藏關閉按鈕,必須等內部工作完成後自動關閉。這是內容功能刻意設定的,不是當機。
欄位說明¶
容器沒有資料欄位,下表是內容 VM 透過 Parameter(ContentWindowParameter)交給容器的設定項,決定彈窗外觀與行為:
| 設定項 | 必填 | 說明/格式/預設 |
|---|---|---|
| Title(標題) | — | 顯示在標題列;空字串時標題列空白 |
| Width(寬) | — | 指定彈窗寬度;0 表示不指定,改用容器基準值(有主視窗時為主視窗寬 −100,無主視窗時退回內建預設 1500) |
| Height(高) | — | 指定彈窗高度;0 表示不指定,改用容器基準值(有主視窗時為主視窗高 −100,無主視窗時退回內建預設 800) |
| NeedCloseButton(顯示關閉鈕) | — | 預設依建構式而定;為 true 時容器顯示關閉按鈕,為 false 時隱藏(搭配「不可中途關閉」的彈窗) |
| IsSizeToContent(貼合內容) | — | 預設 false;為 true 時忽略寬高,視窗自動縮放至內容大小(例如小型進度條) |
尺寸的取捨規則
容器先以「主視窗寬高各減 100」當基準,再與 Parameter 指定的寬高取較小值(Math.Min)。也就是說,內容 VM 指定的尺寸是「上限」,永遠不會超出主視窗範圍;若指定 0 則完全交給基準值。
欄位連動與附加功能¶
這一節說明容器在背後替所有彈窗做了哪些事——也就是「當你開/關一個彈窗時,系統實際發生什麼」。
開窗:ShowContentWindow 的完整流程¶
當任一功能呼叫 Context.ContentWindowService.ShowContentWindow(vm):
- 系統先鎖住主版面(
Container.LayoutIsEnabled = false),讓背景無法操作,達成模態效果。 - 透過
ProgramNavigator.SwitchToContentWindow(vm, Open)把這個彈窗登記到程式導覽紀錄(把目前程式/檢視推入 history 堆疊,記錄「目前開著哪個彈窗」)。若內容 VM 是ERPProgramBaseViewModel,這一步也會自動呼叫其Init()做初始化。 - 建立
ContentWindow並把內容 VM 包進專屬的ContentWindowViewModel:- 容器把內容 VM 設為
Content,再透過App.xaml的 DataTemplate 對應出實際 View 顯示在ContentControl。 - 容器把內容 VM 的
Parameter.Title設為標題列文字。 - 回填參照:容器把自己(
ContentWindowViewModel)回填到內容 VM 的ContentWindowViewModel屬性,內容 VM 之後就能用它來關窗。
- 容器把內容 VM 設為
- 依
Parameter計算尺寸並決定關閉按鈕是否顯示,然後以模態(ShowDialog)方式顯示,阻塞呼叫端直到彈窗關閉。 - 彈窗關閉後:
- 解除主版面鎖定(
LayoutIsEnabled = true)。 - 以
SwitchToContentWindow(vm, Close)登記彈窗關閉。 - 若內容 VM 實作了
IDisposable,自動呼叫Dispose()釋放資源。
- 解除主版面鎖定(
- 回傳
bool:DialogResult == true回true(使用者確認/流程成功),否則回false。呼叫端通常以if (ShowContentWindow(vm)) { ... }判斷使用者是否按了確定。
關窗:內容 VM 如何結束彈窗¶
容器本身不提供業務按鈕,關窗的主動權在內容 VM:
- 內容 VM 把
ContentWindowViewModel.DialogResult = true(確定/成功)或= false(取消)即可關閉彈窗。容器透過RadWindowDialogResult.DialogResult附加屬性綁定到這個值:一旦設值,附加屬性的回呼會把TsWindow(RadWindow)的DialogResult設好並呼叫Close()(RadWindow不會因DialogResult自動關窗,所以靠這個回呼補上關閉動作)。 - 繼承
ERPProgramContentWindowViewModel的內容 VM 內建一個ExitCommand(建構式new TsCommand(ExitExecute)):執行時會把DialogResult設為false,等同「取消」。XAML 的取消按鈕通常綁這個命令。 - 容器標題列的關閉按鈕(X)走
RadWindow自身的關閉流程:直接關閉視窗、不主動設定DialogResult,因此DialogResult維持null,ShowContentWindow視同false(取消)。是否允許用 X 關閉由CanClose控制(見下節)。
隱藏關閉按鈕(不可中途中斷的彈窗)¶
- 預設情況下,包裝用的
ContentWindowViewModel.NeedHideCloseButton = true,視窗載入時會把容器設為不可關閉(CanClose = false),使用者無法靠 X 中途關閉。 - 若
Parameter.NeedCloseButton == true,容器會把NeedHideCloseButton改為false,顯示關閉按鈕,允許使用者自行關閉。 - 換言之,要不要給使用者一個「手動關閉」的出口,由內容功能透過
Parameter決定。
載入後自動執行(進度條型彈窗)¶
- 若內容 VM 額外實作
IContentWindowLoadedAsync,容器在視窗 Loaded 之後會自動await OnContentWindowLoadedAsync()一次。 - 典型用途是「邊顯示進度邊做事,做完自動關窗」:例如
ProgressBarViewModel在此事件裡執行指定的WorkAsync(),並在工作結束(含發生例外)後,於finally區塊把DialogResult = true讓視窗自動關閉。 - 這類彈窗通常隱藏關閉按鈕、並用
IsSizeToContent讓視窗貼合一個小進度條。
無邊框變體(NoBorderContentWindow)¶
- NoBorderContentWindow 共用同一套
ContentWindowViewModel與Parameter機制,且尺寸計算(Math.Min取上限、IsSizeToContent、NeedCloseButton→NeedHideCloseButton)與ContentWindow相同,但套用無邊框、透明背景的自訂ControlTemplate(Border→AdornerDecorator→ContentPresenter),且置中於整個螢幕(WindowStartupLocation=CenterScreen)而非主視窗。 - 適合不需要標題列/邊框的浮動內容。差別在於:它的
Loaded並未真的去隱藏關閉按鈕(對應程式碼目前被註解掉),因此NeedHideCloseButton在這個變體上不會實際生效——它本來就靠無邊框樣板而不顯示標準的關閉鈕。
與其他開窗方式的關係
新功能一律用 ShowContentWindow(搭配繼承 ERPProgramContentWindowViewModel 的 VM)。舊式的 WindowHelper().OpenWindow(...) 直接 new Window() 的寫法已廢棄,是漸進遷移的對象,不要在新程式碼使用。
常見問題¶
彈窗開起來是一片空白,看不到內容?
容器只放一個 ContentControl,實際畫面靠 App.xaml 的 DataTemplate 把「內容 VM 型別」對應到對應的 View。若該內容 VM 沒有在 App.xaml 註冊對應的 DataTemplate,就會顯示空白。請確認對應的 <DataTemplate DataType="..."> 存在。
為什麼有些彈窗沒有關閉按鈕,關不掉?
這是刻意的。預設 NeedHideCloseButton = true 會讓視窗不可關閉;只有當內容功能在 Parameter 指定 NeedCloseButton = true 時才會顯示關閉按鈕。像進度條這類「不可中途中斷」的彈窗會隱藏關閉鈕,等內部工作完成後自動關閉,請耐心等候而非強制結束程式。
呼叫端怎麼知道使用者按了確定還是取消?
ShowContentWindow 會阻塞到彈窗關閉,並回傳 bool:內容 VM 把 DialogResult 設為 true(確定/成功)時回 true,否則回 false。呼叫端標準寫法是 if (Context.ContentWindowService.ShowContentWindow(vm)) { ... }。
彈窗為什麼總是比主視窗小,無法放到很大?
容器以「主視窗寬高各減 100」為基準,並與內容 VM 指定的寬高取較小值,所以彈窗永遠不會超出主視窗範圍。若要更大的可視空間,請先放大主視窗。
彈窗關閉後,背後表單還是鎖住的,怎麼辦?
正常情況下容器在 ShowDialog 結束後會自動把 LayoutIsEnabled 設回 true 解除鎖定。若異常卡住,多半是內容 VM 在關閉流程中拋出例外導致流程中斷;請從內容功能本身排查,容器本身不持有業務邏輯。
相關功能¶
需補截圖清單
本頁為系統機制說明,以下截圖待 runtime 驗收後補拍(放 docs/Other/images/):
contentwindow-step1.png— 一個典型的 ContentWindow 彈窗(含標題列與關閉按鈕)contentwindow-step2.png— 隱藏關閉按鈕的進度條型彈窗(IsSizeToContent 貼合內容)contentwindow-step3.png— NoBorderContentWindow 無邊框透明浮層對照