How to change UserControl in background? - c#

I am stuck with a problem where I want to change user control on different events in background. I am new in MVVM but I am bound to use MVVM only to achive this task. Code structure is little complex to me but still I figured that New Employee form is getting shown on button click but in new window but I want that form to be opened in current window's content. Code is given here which I need to modify to open usercontrol.
public Task<bool?> InitModification(CoreViewModel vm)
{
var tcs = new TaskCompletionSource<bool?>();
_dispatcherService.CurrentDispatcher.BeginInvoke(new Action(() =>
{
bool? result = null;
Window activeWindow = null;
for (var i = 0; i < Application.Current.Windows.Count; i++)
{
var win = Application.Current.Windows[i];
if ((win != null) && (win.IsActive))
{
activeWindow = win;
break;
}
}
if (activeWindow != null)
{
var win = new NewEmp(vm) { Owner = activeWindow };
result = win.ShowDialog();
}
tcs.SetResult(result);
}));
return tcs.Task;
}

My answer will not use your code since I don't find it useful to what you want to achieve. My answer will suffice with a suggestion on how you can achieve changing content control with MVVM
The way I go about it is with every MVVM-project I have a "application-shell" that acts as a wrapper for all my other content and through that you can easily change content. This application is a View and a ViewModel like such.
ShellView
<xmlns:ViewModel="clr-namespace:WhereOurViewModelIs.ViewModel">
<!--More XAML code-->
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:MyViewModel}">
<Views:MyView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:AnotherViewModel}">
<Views:AnotherView/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPage}"/>
ShellViewModel
public class ShellViewModel
{
private BaseViewModel _currentPage{get;set;}
Public BaseViewModel CurrentPage{
get{return _currentPage;}
set{_currentPage = value; OnPropertyChanged();}
}
public ShellViewModel
{
CurrentPage = new MyViewModel();
}
}
Since we don't know how many different pages there is (in theory) we will tell them that need to be a of a type (inherited or an object) of BaseViewModel. This way we don't need to check for every single page and remove redundant code.
Then you set the Datacontext = new ShellViewModel(); in behind code of ShellView.xaml
BaseViewModel
public class BaseViewModel
{
/*This class can contains whatever you want your other ViewModels to be able to do*/
}
Now you need to set up 2 ViewModels with 2 Views just like we did with our shell.
MyViewModel
public class MyViewModel:BaseViewModel
{
/*Contains Properties,methods,private fields. What you want to show on view*/
}
AnotherViewModel
public class AnotherViewModel:BaseViewModel
{
/*Contains Properties,methods,private fields. What you want to show on view*/
}
Now you can set event to your ShellViewModel to change content whenever something happens. Hopefully this can atleast give you some idea how to work with MVVM. Of course you need to set up our ViewModels with properties changed event and other to get everything working, but this is a start for you.
If you find this answer helpful please chose it as an answer since it take some time to make an example like this.

Related

How to change C# wpf background image when opening a new window?

My MainWindow has 4 radio buttons. The user has to choose one, then press on a button that opens up a new window. Depending on the radio button selected, I want to change the background that appears in the new window. This is my code:
public partial class Practice : Window
{
public Practice()
{
InitializeComponent();
if (((MainWindow)Application.Current.MainWindow).BinomialRadio.IsChecked == true)
{
}
else if (((MainWindow)Application.Current.MainWindow).HypergeometricRadio.IsChecked == true)
{
}
else if (((MainWindow)Application.Current.MainWindow).PoissonRadio.IsChecked == true)
{
Background = new ImageBrush(new BitmapImage(new Uri(BaseUriHelper.GetBaseUri(this), "images/poisson_practice_screen.jpg")));
}
else
{
}
}
}
The new window already has a default background that I set in the properties of the XAML code. This code above runs and executes well, but the picture does not change. I found a quick fix, which is basically to remove the background (so that the new window always has a blank background), and then set it every time it opens. Is there any better way to do this?
Thank you to everyone for their help
The reason that the background of the Practice window does not update is because you set its background in the constructor of the window, which only runs when the window is created. In order for it to update, you have to add event handlers on each of the checkboxes for the Checked event and update the background in the handler.
However, the easiest and most recommended way to do this is using data binding. Data binding is a construct in WPF and other frameworks where you declaratively indicate which properties are linked together, so that you don't have to update them manually. No writing tedious event event handlers or keeping track of complicated changes.
Practice.xaml.cs:
public partial class Practice : Window
{
// INotifyPropertyChanged implementation is important!
// Without it, WPF has no way of knowing that you changed your property...
public class PracticeModel : INotifyPropertyChanged
{
private BitmapImage _background;
public BitmapImage Background
{
get => _background;
set { _background = value; PropertyChanged?.Invoke(nameof(Background)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
public Practice()
{
InitializeComponent();
// DataContext specifies which object the bindings are bound to
this.DataContext = new PracticeModel();
}
}
Practice.xaml:
<Window x:Class="MyApp.Practice" Background="{Binding Background}">
<!-- your content here; all other attributes of Window omitted for brevity -->
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public Practice.PracticeModel PracticeModel { get; set; } = new Practice.PracticeModel();
// ...
public OnButtonClicked(RoutedEventArgs e)
{
var window = new Practice();
// DataContext specifies which object the bindings are bound to
window.DataContext = this.PracticeModel;
window.Show();
}
public OnPoissonRadioChecked(RoutedEventArgs e)
{
PracticeModel.Background = new BitmapImage(new Uri(BaseUriHelper.GetBaseUri(this), "images/poisson_practice_screen.jpg"));
}
// likewise for other radio buttons ...
}
MainWindow.xaml:
<Window x:Class="MyApp.MainWindow">
<RadioButton Group="TheButtons" x:Name="BinomialRadio" IsChecked="True" Checked="OnBinomialRadioChecked" />
<RadioButton Group="TheButtons" x:Name="HypergeometricRadio" Checked="OnHypergeometricRadioChecked" />
<RadioButton Group="TheButtons" x:Name="PoissonRadio" Checked="OnPoissonRadioChecked" />
<RadioButton Group="TheButtons" x:Name="TheOtherRadio" Checked="OnTheOtherRadioChecked" />
</Window>
When you change the property on PracticeModel, the PropertyChanged event is fired. This tells WPF that the property has changed, and it automatically updates all of the relevant bindings. This will quickly become very useful when you want to have more than one dynamically updating property. Additionally, data binding can automatically convert from a string or a Uri to an ImageSource, so you might not even need to create the BitmapImage yourself (and if you don't have to, then don't.)
As you might have noticed, there are still event handlers in this code. That's because I didn't want to introduce too much complexity at the same time, and data-binding radio buttons properly can be kind of confusing for someone who is not accustomed to this. I hope this helps!

WPF Listbox not populated with items from ObservableCollection

The main-window is listening for plugging in/out USB-devices. If it is an usb-key/disk it collects a file-list from that device and show that list in a second window.
While debugging I can see that the NewUsbFiles observablecollection get's populated with 117 items. I see that the property UsbFile (before calling the showdialog) has 117 items, but nevertheless the listbox is empty.
Any thoughts ?
The method to populate / create that second window:
NewUsbFiles = new ObservableCollection<UsbFile>();
UpdateNewUsbFiles(driveName);
Application.Current.Dispatcher.Invoke(delegate
{
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles = NewUsbFiles
};
usbFileSelector.ShowDialog();
});
The UsbFile-class:
public class UsbFile
{
public string UsbFileName { get; set; }
public string OnTableFileName { get; set; }
public bool Ignored { get; set; } = false;
public UsbFile(string fileName)
{
var fileInfo = new FileInfo(fileName);
UsbFileName = fileInfo.FullName;
OnTableFileName = $"{fileInfo.CreationTime:yyMMddHHmmsss}_{fileInfo.Name}";
}
}
The XAML of the second window :
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:MainWindow="clr-namespace:PartyPictures.WPF.MainWindow" x:Name="wUsbFileSelector"
x:Class="PartyPictures.WPF.UsbFileSelector"
mc:Ignorable="d"
Title="USB" HorizontalAlignment="Center" VerticalAlignment="Center" WindowStyle="ToolWindow" ScrollViewer.VerticalScrollBarVisibility="Auto" SizeToContent="WidthAndHeight">
<StackPanel x:Name="spUsbFileList">
<ListBox x:Name="ImageListbox"
DataContext="{Binding ElementName=wUsbFileSelector}"
ItemsSource="{Binding UsbFiles}"
Background="AliceBlue" ScrollViewer.HorizontalScrollBarVisibility="Disabled" MinWidth="200" MinHeight="200">
</ListBox>
</StackPanel>
</Window>
The code-behind of the second window :
public partial class UsbFileSelector : Window
{
public ObservableCollection<UsbFile> UsbFiles { get; set; } = new ObservableCollection<UsbFile>();
public UsbFileSelector()
{
InitializeComponent();
}
}
Inside the window you can see InitializeComponent method. It creates all of the stuff defined in XAML and applies all bindings. After binding has been appplied with your empty collecton (that you have created with default property value) the binding will not know about any change of that property, that was the right answer.
But implementing INotifyPropertyChanged is more about viewmodel instances, not visual.
I really suggest you use Dependency Property for windows and controls if you want to bind. There are some reasons for that:
Dependency property setter has built-in notify mechanism.
If you bind one DP to another DP, value is shared in between.
After all, it is WPF approach =)
Here is how your window will look like after change
public partial class UsbFileSelector : Window
{
public static readonly DependencyProperty UsbFilesProperty =
DependencyProperty.Register("UsbFiles", typeof(ObservableCollection<UsbFile>), typeof(UsbFileSelector));
public ObservableCollection<UsbFile> UsbFiles
{
get { return (ObservableCollection<UsbFile>) GetValue(UsbFilesProperty); }
set { SetValue(UsbFilesProperty, value); }
}
public UsbFileSelector()
{
InitializeComponent();
}
}
Also I strongly recommend you to use some WPF inspector tool while developing for the WPF, for example, snoop. You can navigate through the controls and properties while app is running and find issues much quickly you can from the code or from stackoverflow =)
In
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles = NewUsbFiles
};
you are assigning a new value to the UsbFiles property without firing a property change notification for that property.
You could either implement INotifyPropertyChanged and fire the PropertyChanged event or make UsbFiles a dependency property.
Or you pass NewUsbFiles as constructor argument and assign it before calling InitializeComponent
public UsbFileSelector(ObservableCollection<UsbFile> usbFiles)
{
UsbFiles = usbFiles;
InitializeComponent();
}
and call it like this:
var usbFileSelector = new UsbFileSelector(NewUsbFiles)
{
Owner = this
};
Note that if you always pass a new collection, using ObservableCollection isn't actually necessary. You never add or remove elements to/from the collection, so there is no need for a change notification.
Someone posted (and deleted the comment) that I should add
DataContext = this;
To
public UsbFileSelector()
{
InitializeComponent();
DataContext = this;
}
Someone else mentioned (that comment too was deleted) that this was not necessary because of the
DataContext="{Binding ElementName=wUsbFileSelector}"
in the XAML.
BUT it turned out that removing the DataContext line from the XAML and setting it to this in code was the sollution. No idea why but that did it.
EDIT just to make clear that this is not a good solution and works only by accident, try the following:
// this works
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();
// this does not
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
await Task.Delay(10);
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();
All the answers already given are correct, the heart of your problem is the
UsbFiles = NewUsbFiles
which causes the binding to "break" - UsbFiles is no longer pointing to the collection that is bound to the Listbox.
Another possible way to solve this would be to simply leave the bound collection alone and just repopulate the contents.
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles.Clear();
foreach (var uf in NewUsbFiles) {
UsbFiles.Add(uf);
}
};

c# WPF MVVM TabControl with Multiple ViewModels and changing tabs

So I currently have a Window with a TabControl. The MainWindow has its own ViewModel and all the TabItems have their own ViewModels also.
I can easily change tabs from the MainWindow ViewModel through a bound SelectedIndex property. What I would like to do is change to another tab from code that runs within ANOTHER tab viewmodel. Since the Tabs are not part of the MainWindowViewModel, I am looking for a clean way to change the Tab without resorting to code behind to do it.
There are also cases, where I might need to change the tab from something such as a message prompt. I thinking my only way is to create and event and subscribe to that from MainWindowViewModel.
So I solved this with an EventAggregator.
public static class IntAggregator
{
public static void Transmit(int data)
{
if (OnDataTransmitted != null)
{
OnDataTransmitted(data);
}
}
public static Action<int> OnDataTransmitted;
}
First ViewModel sends data.
public class ModifyUsersViewModel : INotifyPropertyChanged
{
private void change_tab(int data)
{
IntAggregator.Transmit(data);
}
}
Second ViewModel receives data and then does something with it.
public class MainWindowViewModel : INotifyPropertyChanged
{
private int _Tab_SelectedIndex = 0;
public int Tab_SelectedIndex
{
get
{
return _Tab_SelectedIndex;
}
set
{
_Tab_SelectedIndex = value;
OnPropertyChanged(new PropertyChangedEventArgs("Tab_SelectedIndex"));
}
}
public MainWindowViewModel()
{
IntAggregator.OnDataTransmitted += OnDataReceived;
}
private void OnDataReceived(int data)
{
Tab_SelectedIndex = data;
}
}
Rather than trying to bind to SelectedIndex, if the TabItems have their own view models, then you can create a property for each of those view models: IsSelected and then bind the TabItem.IsSelected property to that:
<TabItem IsSelected="{Binding IsSelected}">
This prevents the view models from needing to know the index of their corresponding TabItem, something I would argue is a detail that should be specific to the view and something the view model should not concern itself with. What if you add another TabItem or want to change the order? Now you've got changes to make in the view models for something that could be just simple change to the view.

Advice on Views navigation using Caliburn.Micro MVVM WPF

I'm new on Caliburn Micro and want some advice on which path to take to devolop my app interface and navigation between views.
My idea is to have a MainWindow which will contain a menu of buttons, each one related with a specific view. Each view will be stored in a separated WPF UserControl. The mainWindow will also contain a TabControl bound to an ObservableCollection of tabs on viewmodel. Everytime a button on menu is clicked, I want to add a new tab with a ContentPresenter inside that will dynamically load a view and its corresponding viewmodel.
So my questions:
1) Should I use a Screen Collection here?
2) Should the UserControl implement Screen interface?
3) How do I tell MainWindow ViewModel which view to load on the new added tab maintaining viewmodels decoupled?
Thanks to everyone in advance.
UPDATE
After a lot of reading and some help of the community I managed to resolve this. This is the resultant AppViewModel:
class AppViewModel : Conductor<IScreen>.Collection.OneActive
{
public void OpenTab(Type TipoVista)
{
bool bFound = false;
Screen myScreen = (Screen)Activator.CreateInstance(TipoVista as Type);
myScreen.DisplayName = myScreen.ToString();
foreach(Screen miItem in Items)
{
if (miItem.ToString() == myScreen.ToString())
{
bFound = true;
ActivateItem(miItem);
}
}
if (!bFound) ActivateItem(myScreen);
}
public ObservableCollection<MenuItem> myMenu { get; set; }
public ObservableCollection<LinksItem> myDirectLinks { get; set; }
public ICommand OpenTabCommand
{
get
{
return new RelayCommand(param => this.OpenTab((Type) param), null);
}
}
public AppViewModel()
{
OpenTab(typeof(ClientsViewModel));
MenuModel menu = new MenuModel();
myMenu = menu.getMenu();
myDirectLinks = menu.getLinks();
}
public void CloseTab(Screen param)
{
DeactivateItem(param, true);
}
}
I have to keep the ICommand from OpenTabCommand because the name convention of Caliburn.micro doesn't seems to work inside DataTemplate. Hope it could help someone else. Thanks to all
I've done something very similar using Caliburn.Micro, and based it on the SimpleMDI example included with the examples, with a few tweaks to fit my needs.
Much like in the example, I had a main ShellViewModel:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
}
with a corresponding ShellView containing a TabControl - <TabControl x:Name="Items">, binding it to the Items property of the the Conductor.
In this particular case, I also had a ContextMenu on my ShellView, bound (using the Caliburn.Micro conventions), to a series of commands which instantiated and Activated various other ViewModels (usually with a corresponding UserControl, using the ActivateItem method on the Conductor.
public class YourViewModel: Conductor<IScreen>.Collection.OneActive
{
// ...
public void OpenItemBrowser()
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
In that case, I didn't require the ViewModels to be created with any particular dependency, or from any other locations in the program.
At other times, when I've needed to trigger ViewModel from elsewhere in the application, I've used the Caliburn.Micro EventAggregator to publish custom events (e.g. OpenNewBrowser), which can be handled by classes implementing the corresponding interface (e.g. IHandle<OpenNewBrowser>), so your main ViewModel could have a simple Handle method responsible for opening the required View:
public class YourViewModel: Conductor<IScreen>.Collection.OneActive, IHandle<OpenNewBrowser>
{
// ...
public void Handle(OpenNewBrowser myEvent)
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
This section of the documentation will probably be useful, especially the Simple MDI section.
Additional code I mentioned in the comments:
I sometimes use a generic method along these lines ensure that if I have an existing instance of a screen of a particular type, switch to it, or create a new instance if not.
public void ActivateOrOpen<T>() where T : Screen
{
var currentItem = this.Items.FirstOrDefault(x => x.GetType() == typeof(T));
if (currentItem != null)
{
ActivateItem(currentItem);
}
else
{
ActivateItem(Activator.CreateInstance<T>());
}
}
Used like:
public void OpenBrowser()
{
this.ActivateOrOpen<BrowserViewModel>();
}

Correct approach to UserControl creation when using MVVM

This is more of a conceptual question rather than a practical one. I'm just starting to learn the MVVM concept for developing UI , and I've come across a dillema I'm not sure the answer to:
Say I have a main window and a little pop-up window (meaning it's a small window with some UI elements in it). The structure of the program will look something like this:
MainWindow
model <-- MainWindowViewModel.cs <-- MainWindowView.xaml (containing no code-behind)
PopUpWindow (A UserControl)
model <-- PopUpWindowViewModel.cs <-- PopUpWindowView.xaml (containing no code-behind)
*the model is just a bunch of BL classes that are irrelevant for this question.
Now , lets say I want to create a new PopUp window from inside the MainWindowViewModel (or even save an instance of it in a private data-member). What is the correct way of doing so?
The way I see it I can't do something like this :
PopUpWindow pop = new PopUpWindow()
Because it kind of defeats the purpose of abstracting the view from the view model(What if a year from now i'll want to create a better version of the PopUpWindow using the same PopUpWindowViewModel?).
On the other hand , I can't initialize a new instnace of the PopUpWindow using just it's view model (The viewModel as I understand is not supposed to know anything about the view that will use it).
Hope it all makes sense... so what would you do in that situation?
*Just to clarify it further , let's say for argument's sake that the situation I'm describing is a button on the MainWindowView that upon clicking will open a PopUpWindowView.
Thanks in advnace.
I had somewhat a similar dilemma and I'll explain how I solved it.
Let's say you have MainWindow and a SettingsWindow, which you want to display when the SettingsButton is clicked.
You have two respective view models, MainWindowViewModel and SettingsViewModel, which you will be passing as their Window.DataContext properties.
Your MainWindowViewModel should expose an ICommand property named SettingsButtonCommand (or similar). Bind this command to the SettingsButton.Command.
Now your command should invoke something like this:
void OnSettingsButtonClicked()
{
var viewModel = new SettingsViewModel();
var window = new SettingsWindow();
window.DataContext = viewModel;
window.Show();
}
There is a slight issue when you want to use Window.ShowDialog(), because you need to resume execution.
For these cases I have an asynchronous variant of the DelegateCommand:
public sealed class AsyncDelegateCommand : ICommand
{
readonly Func<object, Task> onExecute;
readonly Predicate<object> onCanExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public AsyncDelegateCommand(Func<object, Task> onExecute)
: this(onExecute, null) { }
public AsyncDelegateCommand(Func<object, Task> onExecute, Predicate<object> onCanExecute)
{
if (onExecute == null)
throw new ArgumentNullException("onExecute");
this.onExecute = onExecute;
this.onCanExecute = onCanExecute;
}
#region ICommand Methods
public async void Execute(object parameter)
{
await onExecute(parameter);
}
public bool CanExecute(object parameter)
{
return onCanExecute != null ? onCanExecute(parameter) : true;
}
#endregion
}
You've specifically said that the popup is a UserControl so you can use basic data templating. First create view models for your main window and popup control:
public class MainViewModel : ViewModelBase
{
private PopUpViewModel _PopUp;
public PopUpViewModel PopUp
{
get { return _PopUp; }
set { _PopUp = value; RaisePropertyChanged(() => this.PopUp); }
}
}
public class PopUpViewModel : ViewModelBase
{
private string _Message;
public string Message
{
get { return _Message; }
set { _Message = value; RaisePropertyChanged(() => this.Message); }
}
}
The MainViewModel's PopUp member is initially null, we'll set it to an instance of PopUpViewModel when we want the popup to appear. To do that we create a content control on the main window and set it's content to that member. We also use a data template to specify the type of child control to create when the popup view model has been set:
<Window.Resources>
<DataTemplate DataType="{x:Type local:PopUpViewModel}">
<local:PopUpWindow />
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="Show PopUp" Click="Button_Click_1" HorizontalAlignment="Left"/>
<ContentControl Content="{Binding PopUp}" />
</StackPanel>
I'm doing a big no-no here by creating the view model in the code-behind along with a click handler, but it's just for illustrative purposes:
public partial class MainWindow : Window
{
MainViewModel VM = new MainViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = this.VM;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.VM.PopUp = new PopUpViewModel { Message = "Hello World!" };
}
}
That's it! Click the button, popup window appears underneath it showing the content. Now it may not always be this simple, sometimes you may want to create multiple children on a parent control...in that case you'd set up an ItemsControl, set its panel to a Grid (say) and modify the data templates to set the Margin etc on each element to position them. Or you may not always know what type of view model is going to be created, in which case you need to add multiple data templates for each type you're expecting. Either way you still have good separation of concerns because it is the views that are deciding how to display the content in the view models. The view models themselves still don't know anything about the views and they can be unit-tested etc independently.

Categories

Resources