iT邦幫忙

0

[問題] MVVM架構下,如何多個View共用同一個ViewModel變數

請問如何是否能解決這個程式要多個View去共享一個ViewModel的辦法?
目前這個程式的View架構如下:
MainWindow.xaml (window)
|__ReviewView.xaml (page)
  |__DisplayControlView.xaml (page)
  |__DisplayPanelView.xaml (page)
    |__DisplayView.xaml (page)

ReviewView.xaml用frame容器包含DisplayControlView.xaml跟DisplayPanelView.xaml兩個Page
DisplayPanelView.xaml又往下包含了一個DisplayView.xaml
主要功能是-在DisplayControlView上有按鈕可以開啟圖檔,然後顯示在DisplayView上

目前找過文章說可以View設定DataContext綁定ViewModel,或是反過來,但是都沒很完整的範例示範,還請各位大大能幫幫忙!謝謝!

我試過用Singleton修改過,結果越改越糟糕........囧

以下是我原始的程式片段:
--DisplayControlView.xaml

<Page.DataContext>
    <vm:DisplayViewModel/>
</Page.DataContext>
...
<Button x:Name="btnDpOpen" Style="{DynamicResource ButtonStyle}" Width="120" Command="{Binding OpenFile}">
...

--DisplayView.xaml

<Page.DataContext>
    <vm:DisplayViewModel/>
</Page.DataContext>
...
<Image x:Name="imgDisplayView" Source="{Binding ImagePath}">
	<Image.RenderTransform>
		<TransformGroup>
			<TranslateTransform/>
			<RotateTransform/>
			<ScaleTransform/>
		</TransformGroup>
	</Image.RenderTransform>
</Image>
...

--DisplayViewModel.cs

class DisplayViewModel : ViewModelBase
{
	private DisplayImageModel image { get; set; }

	private ObservableCollection<DisplayImageModel> imagelist = new ObservableCollection<DisplayImageModel>();
	public ObservableCollection<DisplayImageModel> ImageList
	{
		get { return imagelist; }
		set
		{
			imagelist = value;
			OnPropertyChanged();
		}
	}

	public string ImagePath
	{
		get { return image.Path; }
		set
		{
			if (image.Path != value)
			{
				image.Path = value;
				OnPropertyChanged();
			}
		}
	}

	public DisplayViewModel()
	{
		image = new DisplayImageModel();
		ImagePath = @"C:\lenna.png";
	}

	public bool CanExecute()
	{
		return true;
	}

	public RelayCommand OpenFile
	{
		get { return new RelayCommand(openFile, CanExecute); }
	}

	private void openFile()
	{
		string[] picExtName = new string[] { ".PNG", ".JPG", "JEPG", "BMP" };

		OpenFileDialog dlgOpenFile = new OpenFileDialog() 
		{ Filter = "Picture|*.jpg;*.jpeg;*.bmp;*.png|All File|*.*" };
		if (dlgOpenFile.ShowDialog() != true)
		{
			return;
		}
		if (picExtName.Any(System.IO.Path.GetExtension(dlgOpenFile.FileName).ToUpper().Contains))
		{
			ImagePath = dlgOpenFile.FileName;
			OnPropertyChanged();

		}
	}
}

--ViewModelBase.cs

public abstract class ViewModelBase : INotifyPropertyChanged
{
	public event PropertyChangedEventHandler PropertyChanged;

	public void SetValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
	{
		if (property != null)
		{
			if (property.Equals(value))
			{
				return;
			}
		}

		property = value;
		OnPropertyChanged(propertyName);
	}

	protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
	{
		if (PropertyChanged != null)
		{
			PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}
	}
}

--RelayCommond.cs

public class RelayCommand : ICommand
{
	readonly Func<bool> _canExecute;
	readonly Action _execute;

	public RelayCommand(Action execute) : this(execute, null)
	{
	}

	public RelayCommand(Action execute, Func<bool> canExecute)
	{
		_execute = execute ?? throw new ArgumentNullException("execute");
		_canExecute = canExecute;
	}
	
	public event EventHandler CanExecuteChanged
	{
		add
		{
			if (_canExecute != null) CommandManager.RequerySuggested += value;
		}
		remove
		{
			if (_canExecute != null) CommandManager.RequerySuggested += value;
		}
	}

	[DebuggerStepThrough]
	public bool CanExecute(object parameter)
	{
		return _canExecute == null ? true : _canExecute();
	}

	public void Execute(object parameter)
	{
		_execute();
	}
}

1 個回答

0
小魚
iT邦大師 1 級 ‧ 2021-06-23 13:36:17

Xaml? WPF喔.
多個View共用一個ViewModel,
有甚麼問題嗎?

先宣告

private DisplayViewModel viewModel = new DisplayViewModel();

在建構子的地方加入,

DataContext = viewModel;

但是你的DisplayViewModel沒有宣告public可能無法共用.

看更多先前的回應...收起先前的回應...

DisplayControlView開啟的影像無法在DisplayView顯示出來
還是有可以提供的關鍵字或範例參考?
我理解是缺省public private protected時,應該是設定public的
我是有嘗試在建構子裡故意給一個影像路徑預設值
發現都會顯示預設的這個影像,所以猜是開的檔案路徑字串沒正確傳給另一個View,應該Notify也只作用在自己的View上

只是要改成同一個instance,沒什麼頭緒

小魚 iT邦大師 1 級 ‧ 2021-06-23 13:44:14 檢舉

其實不是很懂這需求,
你要直接顯示看要不要寫使用者控制項包在裡面,
或是把影像檔案記下來,
進入DisplayView再打開.

我嘗試全寫在一起這MVVM模式是正確的,但實際上需要模組化才能重複應用DisplayView才分開的
正常使用上也不太是只開一個影像檔,除非是交學校作業 XD

網上搜到是還有用Event傳遞,但我的DisplayView跟DisplayControlView並不在Logical Tree/Visual Tree的直接上下關係,就比較麻煩

對了,大大的辦法不就等同在xaml內的

<Page.DataContext>
    <vm:DisplayViewModel/>
</Page.DataContext>

只差在寫在.cs或是.xaml而已吧?
另外也要『正確』觸發OnPropertyChanged去通知UI更新才行
這也是要解的問題啊!

小魚 iT邦大師 1 級 ‧ 2021-06-23 14:14:33 檢舉

那兩個DisplayViewModel其實是不同一個,
或許你要考慮的是傳遞影片字串,
看是要用一個共用的static參數或是使用參數傳遞的方式.

另外你更新UI會有問題嗎?

大大說的方法有範例嗎?看大大是WPF大師呢,應該MVVM也熟的~^^
基本上,上述問題中就說instance不是同一個,所以兩個View各自為政
嘗試Singleton Pattern改過,DisplayView也沒成功收到字串
也試過在兩者的根結點上的ReviewView去設定過DataContext也是無法把字串傳過去,DisplayView的影像還是出不來

所以想問說,MVVM下如何將ViewModel實例化instance是固定一個給數個View使用的

小魚 iT邦大師 1 級 ‧ 2021-06-23 16:21:00 檢舉

如果方法不拘的話,
WinForm跟WPF其實都可以寫共用的類別跟共用的函式,
讀取或修改的時候直接去改它們就可以,
跟你在哪個檔案裡面沒有關係.

如果你一定要用同一個ViewModel的話,
或許在MainWindow新增一個static的ViewModel試試看,
(不用static設成public也是可以,
但是你就需要一直傳遞MainWindow物件)
然後DataContext就不要寫在Xaml了,
就寫在後端吧.

我是沒有這樣子做過,
不過似乎是可行的.

經過幾天嘗試,目前是用Singleton Pattern勉強符合像MVVM的樣子
對Pure MVVM方法還努力中,有些不甚滿意/images/emoticon/emoticon36.gif
目標是.xaml.cs內的程式碼清空啊!!!!
####DisplayViewModel.cs

加入Double-Checking Lock SingleTon
改建構子修飾詞限制為private

private static readonly object padlock = new object();
private static volatile DisplayViewModel _instance = null;

public static DisplayViewModel Instance
{
	get
	{
		if (_instance == null)
		{
			lock (padlock)
			{
				if (_instance == null)
				{
					_instance = new DisplayViewModel();
				}
			}
		}
		return _instance;
	}
}

private static DisplayImageModel image { get; set; }
...

####DisplayView.xaml & DisplayControlView.xaml

刪除以下的DataContext引用

<Page.DataContext>
    <vm:DisplayViewModel/>
</Page.DataContext>

####DisplayView.xaml.cs & DisplayControlView.xaml.cs

在初始化元件後設定DataContext為Singleton ViewModel

InitializeComponent();
this.DataContext = DisplayViewModel.Instance;
小魚 iT邦大師 1 級 ‧ 2021-06-28 18:02:01 檢舉

清空就不用寫了..
可以運作應該就沒問題了.

我要發表回答

立即登入回答