resolve specific dependency in unity DI/IOC - wpf

While exploring DI/IOC with Unity with WPF, I came across a question and need your feedback. Please consider the following scenario...
================================================================
public interface IDataServices
{
string GetData();
}
================================================================
public class CopyTextDataServices : IDataServices
{
public string GetData()
{
return "copy text from CopyTextDataServices";
}
}
================================================================
public class TextDataServices : IDataServices
{
public string GetData()
{
return "I am injected by setter property injection";
}
}
================================================================
public interface ITextViewModel
{
string LabelContnet { get; set; }
}
================================================================
public class TextViewModel : ITextViewModel
{
public TextViewModel()
{
LabelContnet = "This is from view model";
}
public string LabelContnet { get; set; }
}
================================================================
public partial class MainWindow : Window
{
public MainWindow(ITextViewModel textViewModel)
{
InitializeComponent();
Loaded += MainWindow_Loaded;
DataContext = textViewModel;
}
[Dependency]
public IDataServices Services { get; set; }
containing the event data.</param>
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
LabelLeft.Content = Services.GetData();
}
}
================================================================
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
IUnityContainer container = new UnityContainer();
container.RegisterType<IDataServices, TextDataServices>();
container.RegisterType<IDataServices, CopyTextDataServices>();
container.RegisterType<ITextViewModel, TextViewModel>();
var window = container.Resolve<MainWindow>();
window.Show();
}
}
================================================================
<Window x:Class="TestAppWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" FontSize="20">
<StackPanel>
<Label Content="{Binding Path=LabelContnet,FallbackValue=Left}" HorizontalAlignment="Left" Name="LabelLeft" />
<Label Content="{Binding Path=LabelContnet,FallbackValue=Right}" HorizontalAlignment="Left" Name="LabelRight" />
</StackPanel>
</Window>
===================================================================
Now the result of this appears in the labels is
copy text from CopyTextDataServices
This is from view model
But I want to know if I want to get data from TextDataServices, how can I do that?

The problem is in this line:
container.RegisterType<IDataServices, TextDataServices>();
// This overwrites the previous mapping.
// All dependencies to IDataServices will use CopyTextDataServices.
container.RegisterType<IDataServices, CopyTextDataServices>();
If you want to have both IDataServices, you'll need to register one or both as named instances.
container.RegisterType<IDataServices, TextDataServices>("TextDataServicesName");
container.RegisterType<IDataServices, CopyTextDataServices>("CopyTextDataServicesName");
In your control:
[Dependency("TextDataServicesName")]
public IDataServices Services { get; set; }

Related

WPF how to transfer data between windows (MVVM)?

I know there are a lot of similar questions and I spent two hours by now trying to implementing them but can't proceed. So the problem seems simple. When I don't have a viewmodel, I can set the datacontext to a class and it is very easy to transfer data with that class. But when there is viewmodel, I have to set the datacontext to that and can't find a way to return any value after that. I tried to implement countless solutions to the problem but it seems that they are above my skill level. Thank you so much for your help!
The important parts of my code (its a simple game which i want to save, where save is named by userinput) The first window, where I want to get data from the second window
case Key.Escape: {
Thread t = new Thread(() => {
SaveGameWindow pw = new SaveGameWindow(); //the second window
if ((pw.ShowDialog() == true) && (pw.Name != string.Empty)) //pw.Name always empty
{
ILogicSaveGame l = new LogicSaveGame();
l.Write(pw.Name, "saved_games.txt");
MessageBox.Show("game saved");
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
XAML (from now on everything belongs to the SaveGameWindow):
<Window.Resources>
<local:SaveGameViewModel x:Key="my_viewmodel"/>
</Window.Resources>
<Grid DataContext="{StaticResource my_viewmodel}">
<TextBox Text="{Binding Name}"/> //i want to acces this in the first window
<Button Command="{Binding CloseCommand}"
Content="Save"/>
Code behind:
private readonly SaveGameViewModel vm;
public SaveGameWindow()
{
this.InitializeComponent();
this.vm = this.FindResource("my_viewmodel") as SaveGameViewModel;
if (this.vm.CloseAction == null)
{
this.vm.CloseAction = new Action(() => this.Close());
}
}
Viewmodel
public class SaveGameViewModel : ViewModelBase
{
public SaveGameViewModel()
{
this.CloseCommand = new RelayCommand(() => this.Close());
}
public string Name { get; set; }
public ICommand CloseCommand { get; private set; }
public Action CloseAction { get; set; }
private void Close()
{
this.CloseAction();
}
}
I use galasoft mvvmlightlibs
There are many solutions to this problem. The simplest solution is to use a shared view model for both windows and data binding. Since both windows would share the same DataContext, both have access to the same data or model instance by simply referencing their DataContext property.
But if you prefer to have individual view models, you would choose a different solution.
Solution 1
If you want to use a dedicated view model for each window, you can always use composition and make e.g. an instance SaveGameViewModel a member of MainWindowViewModel. Any class that has access to MainWindowViewModel will also have access to the SaveGameViewModel and its API, either directly or via delegating properties.
This example uses direct access by exposing SaveGameViewModel as a public property of MainWindowViewModel:
SaveGameViewModel.cs
public class SaveGameViewModel : INotifyPropertyChanged
{
private string name;
public string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : INotifyPropertyChanged
{
public SaveGameViewModel SaveGameViewModel { get; set; }
// Allow to create an instance using XAML
public MainWindowViewModel() {}
// Allow to create an instance using C#
public MainWindowViewModel(SaveGameViewModel saveGameViewModel)
=> this.SaveGameViewModel = saveGameViewModel;
}
App.xaml
<Application>
<Application.Resources>
<MainWindowViewModel x:Key="MainWindowViewModel">
<MainWindowViewModel.SaveGameViewModel>
<SaveGameViewModel />
</MainWindowViewModel.SaveGameViewModel>
</MainWindowViewModel>
</Application.Resources>
</Application>
SaveGameWindow.xaml
<Window DataContext="{Binding Source={StaticResource MainWindowViewModel}, Path=SaveGameViewModel}">
<TextBox Text="{Binding Name}" />
<Window>
MainWindow.xaml
<Window DataContext="{StaticResource MainWindowViewModel}">
<Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
private void OnKeyPressed(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
var mainWindowViewModel = this.DataContext as MainWindowViewModel;
string saveGameName = mainWindowViewModel.SaveGameViewModel.Name;
}
}
}
Solution 2
Since you are just showing a dialog, you can store the current instance of the SaveGameViewModel or its values of interest after the dialog has been closed:
MainWindow.xaml.cs
partial class MainWindow : Window
{
private SaveGameViewModel CurrentSaveGameViewModel { get; set; }
private bool IsSaveGameValid { get; set; }
private void ShowDialog_OnSaveButtonClick(object sender, RoutedEventArgs e)
{
var saveGameDialog = new SaveGameWindow();
this.IsSaveGameValid = saveGameDialog.ShowDialog ?? false;
this.CurrentSaveGameViewModel = saveGameDialog.DataContext as SaveGameViewModel;
}
private void OnKeyPressed(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape && this.IsSaveGameValid)
{
string saveGameName = this.CurrentSaveGameViewModel.Name;
}
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<MainWindowViewModel />
</Window.DataContext>
<Window>
SaveGameWindow.xaml
<Window>
<Window.DataContext>
<SaveGameViewModel />
</Window.DataContext>
<TextBox Text="{Binding Name}" />
<Window>

WPF: when move to other page all my ListView data disappear

I have WPF application with 2 pages and this Frame:
<Window>
<Window.DataContext>
<common:ViewModel/>
</Window.DataContext>
<Grid Name="GridMain" Grid.Row="1">
<Frame Name="MyFrame"
NavigationUIVisibility="Hidden"
Source="Pages/home.xaml"/>
</Grid>
</Window>
So i have 2 Pages
Home.xaml
Options.xaml
And my ViewModel:
public class ViewModelBase : INotifyPropertyChanged
{
private ObservableCollection<MyData> files;
public ICommand SliderSpeedValueChangedCommand { get; set; }
public ViewModelBase()
{
Files = new ObservableCollection<MyData>();
SliderSpeedValueChangedCommand = new SliderSpeedValueChangedCommand(this);
}
public ObservableCollection<MyData> Files
{
get { return files; }
set
{
files = value;
NotifyPropertyChanged();
}
}
}
So in my HomePage i have this ListView:
<ListView Name="ListViewFiles"
ItemsSource="{Binding Files}"/>
And the user select files that added into this ListView.
Now when the user switch to the other Page (Options.xaml) and then switch back to HomePage all the files inside this ListView disappear.
Inside this 2 Pages i have also this:
<Page.DataContext>
<viewmodel:ViewModelBase/>
</Page.DataContext>
<Page.Resources>
<vm:ViewModelBase x:Key="ViewModelBase"/>
</Page.Resources>
What i am doing wrong ?
Update
This is how i am switch between Pages:
public partial class MainWindow : Window
{
public ViewModel viewModel;
private Home home;
private Options options;
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
home = new Home();
options = new Options();
}
private void ListBoxMenu_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int index = ListBoxMenu.SelectedIndex;
switch (index)
{
case 0:
MyFrame.Content = home;
break;
case 1:
MyFrame.Content = options;
break;
}
}
}
EDIT
So i have simple command:
public class AddFilesCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public ViewModelBase ViewModel { get; set; }
public AddFilesCommand(ViewModelBase viewModel)
{
ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return !ViewModel.AddingFiles;
}
public void Execute(object parameter)
{
ViewModel.AddFile();
}
}
And inside ViewModelBase c'tor i am initiate this class:
AddFilesCommand = new AddFilesCommand(this);
And simple Button:
<Button Command="{Binding AddFilesCommand}"/>
And after click this Button the function Execute not called.
When you write the following in your XAML, it creates a new instance of the ViewModel class and sets the DataContext property to it:
<Window.DataContext>
<common:ViewModel/>
</Window.DataContext>
Since you are creating a ViewModel programmatically, you should get rid of this markup. Create a single instance of a view model for each view and only do this one, e.g.:
public partial class MainWindow : Window
{
private readonly Home home;
private readonly Options options;
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
home = new Home() { DataContext = new ViewModelBase() };
options = new Options() { DataContext = new ViewModelBase() };
}
private void ListBoxMenu_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int index = ListBoxMenu.SelectedIndex;
switch (index)
{
case 0:
MyFrame.Content = home;
break;
case 1:
MyFrame.Content = options;
break;
}
}
}
And remove this stuff:
<Page.DataContext>
<viewmodel:ViewModelBase/>
</Page.DataContext>
<Page.Resources>
<vm:ViewModelBase x:Key="ViewModelBase"/>
</Page.Resources>

WPF DataContext updated but UI not updated

I have maximum simple app. I want to fill listbox when button pressed. I use binding, window DataContext is updated after some operation, but UI not updated!
Here is the code:
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="432,288.04,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ListBox x:Name="urlsListBox" ItemsSource="{Binding Urls}" HorizontalAlignment="Left" Height="300" Margin="10,10,0,0" VerticalAlignment="Top" Width="417"/>
</Grid>
MainWindow.xaml.cs
namespace WpfApplication1
{
public partial class MainWindow : Window
{
ViewModel model = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = model;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
model.GetUrls();
}
}
}
ViewModel.cs
namespace WpfApplication1
{
class ViewModel
{
private ObservableCollection<Url> Urls { get; set; }
public ViewModel()
{
Urls = new ObservableCollection<Url>();
}
public void GetUrls()
{
for (int i = 0; i < 5; i++)
{
Urls.Add(new Url { link = i.ToString() });
}
}
}
public class Url
{
public string link { get; set; }
}
}
Problem stems from the Urls property within the ViewModel class. You need to make Urls public, otherwise the MainWindow cannot access the property:
ViewModel:
namespace WpfApplication1
{
public class ViewModel
{
//make this public otherwise MainWindow will not have access to it!
public ObservableCollection<Url> Urls { get; set; }
public ViewModel()
{
Urls = new ObservableCollection<Url>();
}
public void GetUrls()
{
for (int i = 0; i < 5; i++)
{
Urls.Add(new Url { link = i.ToString() });
}
}
}
public class Url
{
public string link { get; set; }
}
}
Hope this helps and let me know if you have any questions!
You need to support property change notification. Use the NuGet package manager to reference the MVVM Lite project, derive your ViewModel from ViewModelBase and then change your link property to this:
private string _link;
{
public string link
{
get {return this._link;}
set {this._link=value; RaisePropertyChanged(() => this.link); }
}
}
You'll also need to do this for your URLs property which needs to be public in order for binding to work. Also I know this is a little bit outside the scope of the question but in general you shouldn't use the Click handler like this, the "proper" way is to add a RelayCommand to your ViewModel and bind your button's Command property to that.

Why won't MEF Lazy load work in the ViewModel?

I'm trying to get Lazy to work for a collection in my ViewModel that I'm binding to. The collection loads through MEF fine, but never gets displayed in the bound UI.
Here's the UI:
<Window x:Class="TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ItemsControl ItemsSource="{Binding MyList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding ItemTitle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel>
</Window>
The code-behind class:
public partial class TestWindow : Window
{
public TestWindow()
{
InitializeComponent();
this.DataContext = new TestVM();
}
}
The ViewModel:
public class TestVM : INotifyPropertyChanged, IPartImportsSatisfiedNotification
{
public TestVM()
{
//I'm using a static class to initiate the import
CompositionInitializer.SatisfyImports(this);
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
[ImportMany(typeof(MyItemBase))]
public Lazy<MyItemBase>[] MyList { get; set; }
public void OnImportsSatisfied()
{
this.PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
}
}
The base class for the items, and some inherited test classes:
[InheritedExport]
public class MyItemBase
{
public MyItemBase()
{
}
public string ItemTitle{ get; set; }
}
public class MyItem1: MyItemBase
{
public MyItem1()
{
this.ItemTitle = "Item 1";
}
}
public class MyItem2: MyItemBase
{
public MyItem2()
{
this.ItemTitle = "Item 2";
}
}
This works IF I just remove the Lazy loading. However, I'll need to apply some export attributes later, which means going to Lazy.
the problem is that you want bind to a list of MyItembase object, but your actual binding is to a lazy arrray of MyItembase objects.(as long as you never call .Value for your lazy item nothing will happen)
i my projects i use a private lazy collection for mef and a normal ObservableCollection for wpf. btw i would prefer Constructor injection for your Mef import
public class TestVM : INotifyPropertyChanged, IPartImportsSatisfiedNotification
{
public TestVM()
{
//I'm using a static class to initiate the import
CompositionInitializer.SatisfyImports(this);
this.MyList = new ObservableCollection();
foreach(var lazyitem in _mefList)
{
this.MyList.Add(lazyitem.Value);
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ObservbableCollection<MyItemBase> MyList{ get; set; }
[ImportMany(typeof(MyItemBase))]
private IEnumarable<Lazy<MyItemBase>> _mefList { get; set; }
public void OnImportsSatisfied()
{
//this.PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
}
}

Prism - Commands Not Firing

I've got a view
<UserControl x:Class="Modules.NavigationMenu.Views.NavigationMenuView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel>
<Button Command="{Binding InspectionCommand}">Inspection</Button>
<Button Command="{Binding HandheldCommand}">Handheld</Button>
</StackPanel>
</UserControl>
and a simple view model - but the commands won't seem to fire - can anyone point me in the right direction please?
public class NavigationMenuViewModel : INavigationMenuViewModel
{
public INavigationMenuView View { get; set; }
public NavigationMenuViewModel(IEventAggregator eventAggregator, INavigationMenuView view)
{
View = view;
HandheldCommand = new DelegateCommand<object>(LaunchHandheld);
InspectionCommand = new DelegateCommand<object>(LaunchInspection);
}
private void LaunchInspection(object obj)
{
MessageBox.Show("Inspection Clicked");
}
private void LaunchHandheld(object obj)
{
MessageBox.Show("Handheld Clicked");
}
public DelegateCommand<object> HandheldCommand { get; set; }
public DelegateCommand<object> InspectionCommand { get; set; }
}
My Module just looks like ...
public class NavigationMenuModule : IModule
{
private readonly IUnityContainer _container;
private readonly IRegionManager _regionManager;
public NavigationMenuModule(IUnityContainer container, IRegionManager regionManager)
{
_container = container;
_regionManager = regionManager;
}
#region Implementation of IModule
public void Initialize()
{
RegisterViewsAndServices();
var viewModel = _container.Resolve<INavigationMenuViewModel>();
_regionManager.Regions[RegionNames.MainMenu].Add(viewModel.View);
}
#endregion
#region Protected Methods
protected void RegisterViewsAndServices()
{
_container.RegisterType<INavigationMenuView, NavigationMenuView>();
_container.RegisterType<INavigationMenuViewModel, NavigationMenuViewModel>();
}
#endregion
}
Are you sure that the command binding is working? If you run the app and look in the debug output panel, do you see any binding warnings? Perhaps the DataContext of your UserControl isn't set to your NavigationMenuViewModel.

Resources