Bind UserControl's control binding from MainModule - wpf

I have WPF MVVM application. There i have one user control with popup. When i click on one of the user control's button (Parent Bindings) i wish to show popup. (likewise close)
Command="{Binding Parent.ShowPopupCommand}"
<Popup Name="Popup1" IsEnabled="True"
IsOpen="{Binding DisplayHelper.IsOpenPopup, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
</Popup>
Instead of writing Dependency Property in user control i wrote separate view model with INotifyPropertyChanged interface. On login i am binding popup IsOpen property from Login.cs
RelayCommand _showPopupCommand;
RelayCommand _hidePopupCommand;
public ICommand ShowPopupCommand
{
get
{
if (_showPopupCommand == null)
{
_showPopupCommand = new RelayCommand(param => this.ShowPopup(), null);
}
return _showPopupCommand;
}
}
public ICommand HidePopupCommand
{
get
{
if (_hidePopupCommand == null)
{
_hidePopupCommand = new RelayCommand(param => this.HidePopup(), null);
}
return _hidePopupCommand;
}
}
private void HidePopup()
{
DisplayHelper ds = new DisplayHelper();
ds.IsOpenPopup = false;
}
private void ShowPopup()
{
DisplayHelper ds = new DisplayHelper();
ds.IsOpenPopup = true;
}
but popup is not showing on click.
Please help in this

Your problem is that you create new instances of DisplayHelper each time you run a command, but the View looks for a DisplayHelper property in the ViewModel.
To fix this, I suggest, you set the DisplayHelper as a property in the ViewModel.
I hope it helped, and if you need me to elaborate, feel free to ask. Happy Coding. :)

Related

Master/Detail Toolbar Command binding

I have following App with a toolbar, Masters list and detail view:
The detail is "injected" via ContentControl.
The detail contains a UserControl, which contains a ScrollViewer and so on. At some point there is a "ZoomPanControl" (not mine) which provides a command "FitView".
I want to execute the command "FitView" from my toolbar for the currently active detail view.
My toolbar button looks like this:
<fluent:Button x:Name="FitButton" Command="{Binding ?WhatGoesHere?}" Header="Fit View"/>
I can not figure out how to bind the command property of the toolbar button to the currently active ZoomPanControl. I don't even "see" this control when doing the command binding.
Any hint how this problem is generally solved is highly appreciated.
public static class WpfHelper
{
public static IEnumerable<DependencyObject> GetVisualChildsRecursive(this DependencyObject parent)
{
if (parent == null)
throw new ArgumentNullException("parent");
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
var v = VisualTreeHelper.GetChild(parent, i);
yield return v;
foreach (var c in GetVisualChildsRecursive(v))
yield return c;
}
}
}
//Command Execute
this.DetailView.GetVisualChildsRecursive().OfType<ZoomPanControl>().First().FitView();
//Command CanExecute
this.DetailView.GetVisualChildsRecursive().OfType<ZoomPanControl>().Any();
Here's how I solved the problem. Luckily I have access to the source code of the ZoomPanControl.
First I have implemented a DependencyProperty in the ZoomPanControl for the "FitView" command like this:
public static readonly DependencyProperty FitCommandDepPropProperty = DependencyProperty.Register(
"FitCommandDepProp", typeof (ICommand), typeof (ZoomAndPanControl), new PropertyMetadata(default(ICommand)));
public ICommand FitCommandDepProp
{
get { return (ICommand) GetValue(FitCommandDepPropProperty); }
set { SetValue(FitCommandDepPropProperty, value); }
}
In the "OnApplyTemplate()" method of the control I set the dependency property:
FitCommandDepProp = FitCommand;
In the detail-View of my application I bind the command-dependency-property to my ViewModel like this:
<zoomAndPan:ZoomAndPanControl x:Name="zoomAndPanControl"
FitCommandDepProp="{Binding FitCommand, Mode=OneWayToSource}"
The important part is Mode=OneWayToSource. This "forwards" the command from the ZoomPanControl to my detail-viewmodel.
Detail-viewmodel has property of ICommand to bind to. From this point on I have the command in my viewmodel logic. I have implemented a mechanism to pass the FitCommand to the viewmodel which is bound to the toolbar. You can use a event or whatever you prefer to pass the command around.
The viewmodel of the toolbar has again a ICommand property for the FitCommand.
public ICommand FitCommand
{
get { return _fitCommand; }
set
{
if (Equals(value, _fitCommand)) return;
_fitCommand = value;
NotifyOfPropertyChange(() => FitCommand);
}
}
In the toolbar-view I bind simply to this property:
<fluent:Button x:Name="FitButton" Command="{Binding FitCommand}" Header="Fit View"/>
After that, the view commands are available for each detail-view separately.
But I have no idea how to solve this without access to the source code of the ZoomPanControl.

Closing a View from a ViewModel in MVVM

I have a Floating-window template in which i load a Message-box by initializing the MessageBoxViewModel object to display the message
I want to close this pop up when user clicks on the Close button. How should i do this.
I have written the Close button command in the MessageBoxViewModel .
public class MessageBoxViewModel : ViewModelBase
{
public MessageBoxViewModel ( string messageText)
{
// load all the fields
}
}
private string message;
public string Message
{
get
{
return message;
}
set
{
if (value == message)
return;
message = value;
base.OnPropertyChanged("Message");
}
}
#region Commands
RelayCommand okay;
public ICommand OKAY
{
get
{
if (okay == null)
{
okay = new RelayCommand(
param => this.CallOkay()
);
}
return okay;
}
}
#endregion
void CallOkay()
{
// should write logic to close this window
}
The approach another MVVM framework uses (Caliburn Micro) is essentially just using events from the VM.
However, to extend the idea into a reusable 'module' Caliburn Micro uses a Conductor class which manages the relationship between the lifecycle of the View and the lifecycle of the ViewModel. An interface on the ViewModel which marks it as 'closable' is required, and you do need to write a conductor specific to the window/dialog implementation you are using (assuming it doesn't subclass from standard Window).
Somewhere in your code you have to create a window and bind it to the viewmodel. This is the place where the conductor should be created to manage the relationship (Caliburn has this in its IWindowManager implementation which provides and binds Window instances to a given VM when the ShowPopup/ShowDialog methods are called)
The conductor may look like (a contrived example):
public class WindowConductor
{
private ISupportClose _closeable;
private Window _window;
private bool _closingFromViewModel;
private bool _closingFromView;
public WindowConductor(Window view, ISupportClose closeable)
{
_closeable = closeable;
_window = view;
_window.Closed += WindowClosed;
_closeable.Closed += ViewModelClosed;
}
public void WindowClosed(object sender, EventArgs e)
{
if(_closingFromViewModel) return;
_closingFromView = true;
closeable.Close();
}
public void ViewModelClosed(object sender, EventArgs e)
{
if(_closingFromView) return;
_closingFromViewModel = true;
window.Close();
}
}
Your ISupportClose interface can simply be:
public interface ISupportClose
{
event EventHandler<CloseEventArgs> Closed;
void Close();
}
Then when you create your windows to display a view for a VM:
public void CreateWindow(viewModel)
{
Window window = new Window();
window.DataContext .. // etc etc bind up the view/model
// Wrap the window/vm with the conductor if the view supports the interface
var closeable = viewModel as ISupportClose;
if(closeable != null)
new WindowConductor(window, closeable);
}
I always find this very useful as it splits the concerns into smaller chunks. You don't often use more than 1 maybe 2 window implementations in an app anyway.
It may be worth noting that there is a bit of plumbing code behind all this (in fact a base class Screen provides a standard implementation of lifecycle management etc)
If you aren't using an MVVM framework, I'd highly recommend you do so - writing boilerplate 'glue' has been done already by multiple frameworks
The very nature of MVVM stipulates that the model knows nothing about the window that's reading it.
On solution is that the view model throws an event for the Window code to handle.
In your view model code:
public event EventHandler CallOkayRequested;
void CallOkay()
{
var dg = this.CallOkayRequested;
if(dg != null)
{
dg(this, EventArgs.Empty);
}
}
And in your window code, handle this event:
MyMessageBox()
{
InitializeComponent();
((MessageBoxViewModel)this.DataContext).CallOkayRequested += ModelCallOkayRequested;
}
void ModelCallOkayRequested(object sender, EventArgs args)
{
this.Close();
}
This might be the best way to do it, if, for example, the View Model is performing some other actions before wanting the dialog to close.
If, however, the view model is doing nothing other than relaying the request, it's less code if you bypass the model altogether and use a standard RoutedUICommand.
In your XAML declare a command binding:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandExecuted" />
</Window.CommandBindings>
Attach this command to your button:
<Button Command="ApplicationCommands.Close">
Close
</Button>
And handle the close method in your window code:
private void CloseCommandExecuted(object sender, EventArgs args)
{
this.Close();
}
There are many ways as referenced in Sriram Sakthivel's comment. But using view model event is simplest:
public event Action ViewModelClosed;
void CallOkay()
{
if (ViewModelClosed != null) ViewModelClosed();
}
in MessageBox's code behind:
...
MessageBoxViewModel vm = new MessageBoxViewModel();
vm.ViewModelClosed += () => this.Close();
Another way:
I always use a layer of message box in my view like this:
<UserControl>
<Grid>
<Border>
<!-- contents of my control -->
</Border>
<Border Visibility="{Binding IsVisible,
Converter={StaticResource BooleanToVisibilityConverter}}"
Background="#4000">
<!-- contents of my message box -->
</Border>
</Grid>
</UserControl>
Add a boolean (IsVisible) property to MessageBoxViewModel and bind the Visibility of MessageBox to it. Then simply change its value in CallOkay()

Setting WPF datacontext for a specific control

I'm developing a WPF application and I'm struggling a little bit to understand some of the details of DataContext as it applies to binding. My application uses a business object which is defined like this:
public class MyBusinessObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
// enumerations for some properties
public enum MyEnumValues
{
[Description("New York")]
NewYork,
[Description("Chicago")]
Chicago,
[Description("Los Angeles")]
LosAngeles
}
// an example property
private string _myPropertyName;
public string MyPropertyName
{
get { return _myPropertyName; }
set
{
if (_myPropertyName == value)
{
return;
}
_myPropertyName = value;
OnPropertyChanged(new PropertyChangedEventArgs("MyPropertyName"));
}
}
// another example property
private MyEnumValues _myEnumPropertyName;
public MyEnumValues MyEnumPropertyName
{
get { return _myEnumPropertyName; }
set
{
if (_myEnumPropertyName== value)
{
return;
}
_myEnumPropertyName= value;
OnPropertyChanged(new PropertyChangedEventArgs("MyEnumPropertyName"));
}
}
// example list property of type Widget
public List<Widget> MyWidgets { get; set; }
// constructor
public MyBusinessObject()
{
// initialize list of widgets
MyWidgets = new List<Widget>();
// add 10 widgets to the list
for (int i = 1; i <= 10; i++)
{
MyWidgets.Add(new Widget());
}
// set default settings
this.MyPropertyName = string.empty;
}
}
As you can see, I have some properties that are declared in this class one of which is a list of Widgets. The Widget class itself also implements INotifyPropertyChanged and exposes about 30 properties.
My UI has a combobox which is bound to my list of Widgets like this:
MyBusinessObject myBusinessObject = new MyBusinessObject();
public MainWindow()
{
InitializeComponent();
this.DataContext = myBusinessObject;
selectedWidgetComboBox.ItemsSource = myBusinessObject.MyWidgets;
selectedWidgetComboBox.DisplayMemberPath = "WidgetName";
selectedWidgetComboBox.SelectedValuePath = "WidgetName";
}
The majority of the controls on my UI are used to display the properties of a Widget. When my user selects a Widget from the combobox, I want these controls to display the properties for the selected Widget. I'm currently achieving this behavior by updating my window's DataContext in the SelectionChanged event handler of my combobox like this:
private void selectedWidgetComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.DataContext = selectedWidgetComboBox.SelectedItem;
}
This allows me to bind my controls to the appropriate Widget property like this:
<TextBox Text="{Binding WidgetColor}"></TextBox>
However, not all of the controls in my UI are used to display Widget properties. Some of the controls need to display the properties from MyBusinessObject (for example: MyPropertyName defined above). In this scenario, I can't simply say:
<TextBox Text="{Binding MyPropertyName}"></TextBox>
...because the DataContext of the window is pointing to the selected Widget instead of MyBusinessObject. Can anyone tell me how I set the DataContext for a specific control (in XAML) to reference the fact that MyPropertyName is a property of MyBusinessObject? Thank you!
Instead of changing the DataContext of your window, you should add a property to your MyBusinessObject class like this one:
private Widget _selectedWidget;
public Widget SelectedWidget
{
get { return _selectedWidget; }
set
{
if (_selectedWidget == value)
{
return;
}
_selectedWidget = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedWidget"));
}
}
Then bind SelectedWidget to the SelectedItem property of your combobox. Anywhere that you need to use the widget's properties you can do this:
<TextBox Text="{Binding Path=SelectedWidget.WidgetColor}"></TextBox>
try
<TextBox Text="{Binding MyBusinessObject.MyPropertyName}"></TextBox>
this works if MyBusinessObject is the datacontext of the textbox and MyPropertyName is a property of MyBusinessObject
Also, Here is a good article to clarify binding
hope this helps
EDIT 1:
use a relative binding like this:
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfControl}}}"
So the relatve binding allows you to look up the visual tree to another UI element and use its datacontext. I would consider wrapping your window's contents in a grid. and wet your windows datacontext to the businessobject and the grids datacontext to the widget. That way you can always use the parent window's datacontext through the realtive source binding.
so use the following if your window's datacontext is your business object
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"

How to Access SelectedItem in one Page on a Separate Page

I have two datagrids separately on two pages, say a parentgrid on a parent-page and a childgrid on a child-page. how to access the selecteditem of parent-page to the child-page ?
when the both the datagrids are placed on the same page, the selecteditem works. but when I place the grids separately on each page, it doesn't work.
XAML for the ParentPage
<Grid.Datacontext>
<local:MainViewModel/>
</Grid.Datacontext>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Persons}" SelectedItem="{Binding SelectedHost, Mode=TwoWay}" SelectionChanged="DataGrid_SelectionChanged"/>
codebehind for the ParentPage
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ChildPage _page = new ChildPage();
this.NavigationService.Navigate(_page);
}
XAML for child page
<DataGrid x:Name="ChildDatagrid" Margin="12,104,81,266" ItemsSource="{Binding Details}"/>
MainViewModel
//Datacontext
public MainViewModel()
{
this.Persons = Person.GetPersons();
}
// for Person Datagrid
private ObservableCollection<Person> personValues;
public ObservableCollection<Person> Persons
{
get { return personValues; }
set { this.SetProperty<ObservableCollection<Person>>(ref this.personValues, value); }
}
//for the PersonDetails datagrid
public ObservableCollection<PersonDetails> Details
{
get
{
if (this.Selectedperson == null)
{
return null;
}
return this.LoadDetails(this.Selectedperson.PersonID);
}
}
// method to load the persondetails data
private ObservableCollection<PersonDetails> LoadDetails(int personID)
{
ObservableCollection<PersonDetails> details = new ObservableCollection<PersonDetails>();
foreach (PersonDetails detail in PersonDetails.GetDetails().Where(item => item.PersonID == personID))
{
details.Add(detail);
}
return details;
}
// SelectedPerson Property
private Person selectedPersonValue;
public Person Selectedperson
{
get { return selectedPersonValue; }
set
{
this.SetProperty<Person>(ref this.selectedPersonValue, value);
this.RaiseNotification("Details");
}
}
You should make a ViewModel or Object, pass them into both pages and bind your grids to it. This way they will stay in sync.
Alternate option is to use and EventAggregator to sent messages between your pages.
If you're using WPF you should take a look into Prism. A lot of build-in functionality exists.
EDIT: Post changed to reflect new information;
I've never used the NavigationService with WPF before, so I'm not 100% sure what is available to you, so apologies if I miss something.
However, if you move your Details Collection to your Child Form, and make it a standard property;
private ObservableCollection<PersonDetails> _Details;
public ObservableCollection<PersonDetails> Details {
get { return _Details; }
set {
if (value.Equals(_Details) == false)
{
_Details = value;
OnPropertyChanged("Details");
}
}
}
Assuming you have a reference to your MainViewModel, you can then navigate to your page using;
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ChildPage _page = new ChildPage();
_page.Details = MainViewModel.LoadDetails(PersonID);
this.NavigationService.Navigate(_page);
}
As a note, I don't like the idea of calling the Navigiate Service from the Code behind, as this makes the code messy and difficult to test.
Jesse Liberty has a nice post on using the MVVM Light Framework, and allowing the ViewModel to call the Navigate Service Directly;
http://jesseliberty.com/2012/01/17/calling-navigate-from-the-view-model/

How keep alive user control?

I have wizard project that works with ContentControl which contains user controls.
I do the instantiation through the XAML file at my main window:
<DataTemplate DataType="{x:Type ViewModel:OpeningViewModel}">
<view:OpeningView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:SecondUCViewModel}">
<view:SecondUCView/>
</DataTemplate>
But when I navigate between the UC's it seems that the UC's aren't works like "keep alive", Every UC switching creates new instance. How can I avoid it?
I want create for every UC just one instance and navigate between those instances only without creating new instances.
I know how write singleton but my project based on MVVM and I'm quite new at WPF so I'm not sure what is the best way to do this.
Thanks
Update:
Here the code of the viewModel:
In the viewModel I have :
private ObservableCollection _pages = null;
private NavigationBaseViewModel _currentPage;
#endregion
#region Properties
public int CurrentPageIndex
{
get
{
if (this.CurrentPage == null)
{
return 0;
}
return _pages.IndexOf(this.CurrentPage);
}
}
public NavigationBaseViewModel CurrentPage
{
get { return _currentPage; }
private set
{
if (value == _currentPage)
return;
_currentPage = value;
OnPropertyChanged("CurrentPage");
}
}
private ICommand _NavigateNextCommand;
public ICommand NavigateNextCommand
{
get
{
if (_NavigateNextCommand == null)
{
_NavigateNextCommand = new RelayCommand(param => this.MoveToNextPage(), param => CanMoveToNextPage);
}
return _NavigateNextCommand;
}
}
private ICommand _NavigateBackCommand;
public ICommand NavigateBackCommand
{
get
{
if (_NavigateBackCommand == null)
{
_NavigateBackCommand = new RelayCommand(param => this.MoveToPreviousPage(), param => CanMoveToPreviousPage);
}
return _NavigateBackCommand;
}
}
private bool CanMoveToNextPage
{
get
{
return this.CurrentPage != null && this.CurrentPage.CanMoveNext;
}
}
bool CanMoveToPreviousPage
{
get { return 0 < this.CurrentPageIndex && CurrentPage.CanMoveBack; }
}
private void MoveToNextPage()
{
if (this.CanMoveToNextPage)
{
if (CurrentPageIndex >= _pages.Count - 1)
Cancel();
if (this.CurrentPageIndex < _pages.Count - 1)
{
this.CurrentPage = _pages[this.CurrentPageIndex + 1];
}
}
}
void MoveToPreviousPage()
{
if (this.CanMoveToPreviousPage)
{
this.CurrentPage = _pages[this.CurrentPageIndex - 1];
}
}
And the ContentControl which contains the UC`s binded to CurrentPage
You can do that by hardcoding the UserControls in XAML, instead of using DataTemplates. DataTemplates will create new Controls every time they are instantiated. However, since you use MVVM, you could also move all data you want persisted between the changes to the ViewModels, and make sure that the ViewModel objects are always the same. Then, the DataTemplates would still create new controls, but they would contain the same information as before.
I have recently come up against the same issue with my views in MVVM. Basically, I wanted to cache views that took a while to render. If you are familiar with the ViewModelLocator, this approach should be straight forward.
In the client (e.g. WPF) project I created a ViewLocator class that looked like this:
public class ViewLocator : ObservableObject
{
#region Properties
private View _myView;
public View MyView
{
get { return _myView; }
set { Set(() => MyView, ref _myView, value); }
}
#endregion Properties
#region Constructors
public ViewLocator()
{
RegisterViews();
}
#endregion Constructors
#region Private Methods
private void RegisterViews()
{
MyView = new View();
}
#endregion Private Methods
}
And to use this in a data template I specified the ViewLocator as an static application resource so only one instance is ever instantiated - in my case I put it in in App.xaml. To use the ViewLocator and it's "View" properties, I did the following:
<vl:ViewLocator x:Key="ViewLocator" />
<DataTemplate DataType="{x:Type vm:ViewModel}">
<ContentControl Content="{Binding Source={StaticResource ViewLocator}, Path=MyView}" />
</DataTemplate>
By doing it this way, each view is only instantiated once and can be reused.

Resources