I have a static bool property in a Model class, which I expose to two different ViewModel classes. One of these ViewModel's has a bool property linked to said static property and is bound to the Visibility of a button via a converter. This can then be set within that ViewModel to true or false and the button's visibility changes accordingly. (The instance of this ViewModel is set in the XAML of the View, via DataContext, in which the button resides)
I want to be able to change this buttons visibility from within a different View, and I thought that by having a property in my separate View's ViewModel that is also linked to my static bool in my original model, I could do this, but it isn't doing anything.
Here's my code:
MainModel
public class MainModel
{
static bool _ButtonIsVisible = true;
public static bool ButtonIsVisible
{
get { return _ButtonIsVisible; }
set { _ButtonIsVisible = value; }
}
}
MainViewModel
class MainViewModel: ObserveableObject
{
public bool ButtonIsVisible
{
get { return MainModel.ButtonIsVisible; }
set
{
MainModel.ButtonIsVisible = value;
RaisePropertyChanged("ButtonIsVisible");
}
}
}
MainView
<Window x:Class="MVVM.MainWindow"
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:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:MVVM"
mc:Ignorable="d"
Title="MainWindow" Width="1920" Height="1080" WindowState="Maximized" WindowStyle="None">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<Button Visibility="{Binding ButtonIsVisible, Converter={StaticResource BoolToVisConverter}}" />
</Window>
ButtonIsVisible from MainViewModel is changed within a command and this works as is expected. This is where my troubles occur.
AnotherViewModel
class AnotherViewModel: ObserveableObject
{
public bool ButtonIsVisible
{
get { return MainModel.ButtonIsVisible; }
set
{
MainModel.ButtonIsVisible = value;
RaisePropertyChanged("ButtonIsVisible");
}
}
}
An instance of AnotherViewModel is created via the DataContext of it's corresponding view, and a command is bound to a button within this view in which I change the ButtonIsVisible property from AnotherViewModel, at which point I would expect my button from my MainView to change, seeing that both ViewModels get and set the values of the properties in question from a static property in my MainModel, but this isn't working.
Can anyone tell me what I'm doing wrong?
As per your comments, you've got something like this:
<ContentControl Content="{Binding ViewModel}" />
And when you want to show it, you are doing this:
ViewModel = new AnotherViewModel();
So what you need in your AnotherViewModel is:
MainViewModel MVM;
public AnotherViewModel(MainViewModel _mvm)
{
this.MVM=_mvm;
}
You must then change your AnotherVieModel Instantation to:
ViewModel = new AnotherViewModel(this);
And when you want to change the button's visibility,you'll just need to do this:
this.MVM.ButtonIsVisible=true;
As I told you,this is just a way to do it, but I think is pretty straightforward and understable. If you've got doubts, feel free to ask.
Related
I wrote code which should navigate between user controls in WPF application using MVVM, but I realised that this code doesn't work.
From window LoginView I want to change the view to VotingCardView.
Actually, after clicking on the button in the LoginView, the method DisplayVCV gets executed, but the view is not going to change. What am I doing wrong?
MainView.xaml:
<Window x:Class="ElectionCalculator.View.MainView"
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:local="clr-namespace:ElectionCalculator"
xmlns:v="clr-namespace:ElectionCalculator.View"
xmlns:vm="clr-namespace:ElectionCalculator.ViewModel"
mc:Ignorable="d"
Title="Election calculator" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<ContentControl Content="{Binding ViewModel}" />
</Window>
LoginView.xaml:
<UserControl x:Class="ElectionCalculator.View.LoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ElectionCalculator.View"
xmlns:vm="clr-namespace:ElectionCalculator.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Command="{Binding DataContext.DisplayVC, RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=OneWay}" Margin="161,147,47,124" />
</Grid>
</UserControl>
MainViewModel.cs
class MainViewModel : BaseViewModel
{
public BaseViewModel ViewModel { get; set; }
public MainViewModel()
{
ViewModel = new LoginViewModel();
}
public ICommand DisplayVC { get { return new RelayCommand(DisplayVCV); } }
public void DisplayVCV()
{
ViewModel = new VotingCardViewModel();
MessageBox.Show("DisplayVCCommandExecuted");
}
}
Your ViewModel property implementation doesn't raise a PropertyChanged event when the value changes. This is usually done via an INotifyPropertyChanged implementation. Because of that, your view doesn't get notified that something has changed.
In your case, this means that you need a backing field for your ViewModel property and implement your ViewModel property similar to this:
private BaseViewModel _viewModel;
public BaseViewModel ViewModel
{
get { return _viewModel; }
set
{
if(_viewModel != value)
{
_viewModel = value;
OnPropertyChanged("ViewModel");
}
}
}
Since you are already deriving from BaseViewModel I assume that the method OnPropertyChanged (or some method with a similar name) is implemented there. It is also quite common that you don't have to specify the property name ("ViewModel") as an argument, since lots of implementations use the [CallerMemberName] attribute for this purpose.
Visual Studio does not show design time data with DesignInstance attribute. I have checked DesignInstance with/without MVVM Light. I have spend a lot of time to fix the issue (checked similar queestions on StackOverflow too) but DesignInstance simply does not work.
Project:
SearchIdView.
SearchIdViewModel - real View Model.
DesignSearchIdViewModel - inherits from SearchIdViewModel and contains design time data (properties are assigned in constructor).
Environment:
VS2013 SP3
Net 4.0
MvvmLight 5.0.2.0
SearchIdView.xaml
<Window x:Class="App1.View.SearchIdView"
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:ignore="http://www.ignore.com"
xmlns:design="clr-namespace:App1.Design"
mc:Ignorable="d ignore"
DataContext="{Binding SearchId, Source={StaticResource Locator}}"
d:DataContext="{d:DesignInstance d:Type=design:DesignSearchIdViewModel,IsDesignTimeCreatable=True}"
>
<Grid>
<TextBlock Text="{Binding Test}" />
</Grid>
SearchIdViewModel.cs
Property from SearchIdViewModel
public const string TestPropertyName = "Test";
private string _test;
public string Test
{
get
{
return _test;
}
set
{
Set(TestPropertyName, ref _test, value);
}
}
Do you have any idea why DesignInstance does not work in this case?
Workaround
remove d:DataContext from view
add interface ISearchIdViewModel (it is empty)
SearchIdViewModel inherits from ISearchIdViewModel
change ViewModelLocator (below)
ViewModelLocator.cs
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<ISearchIdViewModel,Design.DesignSearchIdViewModel>();
}
else
{
SimpleIoc.Default.Register<ISearchIdViewModel, SearchIdViewModel>();
}
}
public SearchIdViewModel SearchId
{
get { return (SearchIdViewModel) ServiceLocator.Current.GetInstance<ISearchIdViewModel>(); }
}
}
Your d:DesignInstance declaration is malformed. You specify the property name d:Type instead of Type, so the property is not assigned correctly. Either replace d:Type with Type, or leave the property name off entirely and let it be inferred as the default property.
d:DataContext="{d:DesignInstance d:Type=design:DesignSearchIdViewModel,
IsDesignTimeCreatable=True}"
Should become:
d:DataContext="{d:DesignInstance Type=design:DesignSearchIdViewModel,
IsDesignTimeCreatable=True}"
Or, alternatively:
d:DataContext="{d:DesignInstance design:DesignSearchIdViewModel,
IsDesignTimeCreatable=True}"
(line wrapping added for readability)
Another cause that might make d:DesignInstance not to work is that all data must be properties not just public variables of mock class! I know that it was not your problem, but it should be checked if for someone it does not work.
Will not work with:
public class MockFile
{
public FilePRJO FilePRJO = new FilePRJO();
}
But it will work with:
public class MockFile
{
public FilePRJO _filePRJO = new FilePRJO();
public FilePRJO FilePRJO
{
get
{
return _filePRJO;
}
}
}
In my solution; I have two projects: One is a WPF UserControl Library, and the other is a WPF Application.
The usercontrol is pretty straightforward; it's a label and a combo box that will show the installed printers.
In the WPF application; I want to use this usercontrol. The selected value will be stored in user settings.
The problem I'm having is that I can't seem to get the proper binding to work. What I need to happen is to be able to set the SelectedValue of the UserControl when the MainWindow loads; as well as access the SelectedValue of the UserControl when I go to save my settings.
My code is below, could someone point me in the right direction?
PrintQueue user control:
<UserControl x:Class="WpfControls.PrintQueue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:wpfControls="clr-namespace:WpfControls"
mc:Ignorable="d">
<UserControl.DataContext>
<wpfControls:PrintQueueViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Content="Selected Printer:"></Label>
<ComboBox ItemsSource="{Binding Path=PrintQueues, Mode=OneWay}" DisplayMemberPath="Name" SelectedValuePath="Name" Width="200" SelectedValue="{Binding Path=SelectedPrinterName, Mode=TwoWay}"></ComboBox>
</StackPanel>
</Grid>
</UserControl>
Print Queue Codebehind:
public partial class PrintQueue : UserControl
{
public static readonly DependencyProperty CurrentPrinterNameProperty =
DependencyProperty.Register("CurrentPrinterName", typeof (string), typeof (PrintQueue), new PropertyMetadata(default(string)));
public string CurrentPrinterName
{
get { return (DataContext as PrintQueueViewModel).SelectedPrinterName; }
set { (DataContext as PrintQueueViewModel).SelectedPrinterName = value; }
}
public PrintQueue()
{
InitializeComponent();
DataContext = new PrintQueueViewModel();
}
}
PrintQueue View Model:
public class PrintQueueViewModel : ViewModelBase
{
private ObservableCollection<System.Printing.PrintQueue> printQueues;
public ObservableCollection<System.Printing.PrintQueue> PrintQueues
{
get { return printQueues; }
set
{
printQueues = value;
NotifyPropertyChanged(() => PrintQueues);
}
}
private string selectedPrinterName;
public string SelectedPrinterName
{
get { return selectedPrinterName; }
set
{
selectedPrinterName = value;
NotifyPropertyChanged(() => SelectedPrinterName);
}
}
public PrintQueueViewModel()
{
PrintQueues = GetPrintQueues();
}
private static ObservableCollection<System.Printing.PrintQueue> GetPrintQueues()
{
var ps = new PrintServer();
return new ObservableCollection<System.Printing.PrintQueue>(ps.GetPrintQueues(new[]
{
EnumeratedPrintQueueTypes.Local,
EnumeratedPrintQueueTypes.Connections
}));
}
}
Main Window:
<Window x:Class="WPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfControls="clr-namespace:WpfControls;assembly=WpfControls" xmlns:wpfApp="clr-namespace:WPFApp"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<wpfApp:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<wpfControls:PrintQueue CurrentPrinterName="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.PrinterName, Mode=TwoWay}"></wpfControls:PrintQueue>
</StackPanel>
</Grid>
</Window>
Main Window View Model:
public class MainWindowViewModel : ViewModelBase
{
private string printerName;
public string PrinterName
{
get { return printerName; }
set
{
printerName = value;
NotifyPropertyChanged(() => PrinterName);
}
}
public MainWindowViewModel()
{
PrinterName = "Lexmark T656 PS3";
}
}
Controls in a library need to expose DependencyProperties that you can bind to in your view. Just like WPF's TextBox exposes a Text property.
Your PrintQueue control doesn't expose anything, and instead keeps all its state in a viewmodel that nothing outside can access. Your MainWindowViewModel has no way of getting at the stuff inside PrintQueueViewModel.
You need to expose SelectedPrinterName as a DependencyProperty in the code behind of your PrintQueue xaml. Then in MainWindow.xaml you can bind it to MainWindowViewModel.PrinterName.
If you want to user ViewModels all the way through instead, then MainWindowViewModel should be creating PrintQueueViewModel itself so it can access the properties within.
As per your update / comment:
Unfortunately DependencyProperties don't work like that. The getters/setters aren't even used most of the time, and they should ONLY update the property itself. You're sort of halfway between two worlds at the moment.
If I were in your position, and assuming you can change the library so PrintQueue.xaml doesn't have a hardcoded VM instance in the view, I would just create the PrintQueueViewModel yourself. That's how MVVM is supposed to work:
ViewModel:
public class MainWindowViewModel : ViewModelBase
{
public PrintQueueViewModel PrintQueue { get; private set; }
public MainWindowViewModel()
{
PrintQueue = new PrintQueueViewModel();
PrintQueue.SelectedPrinterName = "Lexmark T656 PS3";
}
}
View:
<Window x:Class="WPFApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfControls="clr-namespace:WpfControls;assembly=WpfControls" xmlns:wpfApp="clr-namespace:WPFApp"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<wpfApp:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<wpfControls:PrintQueue DataContext="{Binding PrintQueue}"/>
</StackPanel>
</Grid>
</Window>
Again though, control libraries generally don't have view models, and expose their state via dependency properties since they're designed to be used in XAML.
Component libraries may expose view models, but in that case they wouldn't hard code the view model in the view.
Did you write the library? If not, how did the author expect people to use it?
I think with this small changes everything should work
<ComboBox ItemsSource="{Binding Path=PrintQueues, Mode=OneWay}" DisplayMemberPath="Name" Width="200" SelectedItem="{Binding Path=SelectedPrinter, Mode=TwoWay}"></ComboBox>
private System.Printing.PrintQueue selectedPrinter;
public System.Printing.PrintQueue SelectedPrinter
{
get { return selectedPrinter; }
set
{
selectedPrinter = value;
NotifyPropertyChanged(() => SelectedPrinter);
}
}
Now from the main window you can modify SelectedPrinter on the viewmodel and the change should be reflected on the view
(PrintQueue.DataContext as PrintQueueViewModel).SelectedPrinter = ...
I tried your code and your bindings of the PrintQueueView to the corresponding view model work fine. Your problem is that the MainWindowViewModel does not know about the PrintQueueViewModel and thus cannot retrieve the value of the selected printer when the main window closes (I guess that is the scenario you want to implement).
The quickest solution to your problem would be to do the following steps:
In MainWindow.xaml, give PrintQueue a Name so you can access it in the code behind
In MainWindow.xaml.cs, override the OnClosing method. In it you can retrieve the view model as follows: var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;. After that you can retrieve the selected value and save it or whatever.
In the MainWindow constructor after InitializeComponent, you can retrieve your saved value from a file and set it on the PrintQueueViewModel by retrieving it the same way as in the previous step.
Whole code in MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Retrieve your selected printer here; in this case, I just set it directly
var selectedPrinter = "Lexmark T656 PS3";
var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;
viewModel.SelectedPrinterName = selectedPrinter;
}
protected override void OnClosing(CancelEventArgs e)
{
var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;
var selectedPrinterName = viewModel.SelectedPrinterName;
// Save the name of the selected printer here
base.OnClosing(e);
}
}
Please remember that the major point of view models is the ability to unit-test GUI logic and to disconnect GUI appearance and logic. Your view models should not be able to retrieve all the possible printers of your system but should obtain these values by e.g. Dependency Injection. I would advise you to read about SOLID programming.
I have User control which looks like this:
public partial class TopBarUserControl : UserControl
{
public System.Windows.Visibility menuVisibility { get; set; }
public TopBarUserControl()
{
InitializeComponent();
}
}
And I render it in my other page with:
<local:TopBarUserControl />
It works fine but I want to hide some part of my Control. So I pass the parameter to control with:
<local:TopBarUserControl menuVisibility="Collapsed" />
But I don't know how to make it works fine (hide my elements).
What I have tried:
in xaml control:
Visibility="{Binding menuVisibility}"
or set it in code behind but I don't know where it should be setted.
Two things here, first you should ideally declare the property as a DependencyProperty, so you can bind against it and get automatic change notification to update the UI at the right time. Then, you need to correct the binding, so that it points to the property declared at the UserCoontrol level (as you wrote it, it binds to the DataContext, which may not be set.
Try this, in XAML:
<UserControl x:Class="WpfApplication1.TopBarUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="MainControl"> <!--Give a XAML name to the whole control to bind to properties declared in code-behind-->
<Menu Visibility="{Binding ElementName=MainControl, Path=MenuVisibility}"/>
</UserControl>
Notice the binding has an ElementName matching the x:Name at the control level.
The convert your property to DependencyProperty:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class TopBarUserControl : UserControl
{
public static readonly DependencyProperty MenuVisibilityProperty = DependencyProperty.Register("MenuVisibility", typeof(Visibility), typeof(TopBarUserControl), null);
public Visibility MenuVisibility
{
get { return (Visibility)GetValue(MenuVisibilityProperty); }
set { SetValue(MenuVisibilityProperty, value); }
}
public TopBarUserControl()
{
InitializeComponent();
}
}
}
This is my first time working with a WPF datagrid. From what I understand I am supposed to bind the grid to a public propery in my viewmodel. Below is the ViewModel code, as I step through the debugger GridInventory is getting set to List containing 2606 records however these records never show in the datagrid. What am I doing wrong?
public class ShellViewModel : PropertyChangedBase, IShell
{
private List<ComputerRecord> _gridInventory;
public List<ComputerRecord> GridInventory
{
get { return _gridInventory; }
set { _gridInventory = value; }
}
public void Select()
{
var builder = new SqlConnectionBuilder();
using (var db = new DataContext(builder.GetConnectionObject(_serverName, _dbName)))
{
var record = db.GetTable<ComputerRecord>().OrderBy(r => r.ComputerName);
GridInventory = record.ToList();
}
}
}
My XAML is
<Window x:Class="Viewer.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="InventoryViewer" Height="647" Width="1032" WindowStartupLocation="CenterScreen">
<Grid>
<DataGrid x:Name="GridInventory" ItemsSource="{Binding GridInventory}"></DataGrid>
<Button x:Name="Select" Content="Select" Height="40" Margin="600,530,0,0" Width="100" />
</Grid>
</Window>
i think you need call raisepropertychanged event in your GridInventory setter so that view can get notified.
public List<ComputerRecord> GridInventory
{
get { return _gridInventory; }
set
{ _gridInventory = value;
RaisePropertyChanged("GridInventory");
}
}
The page's datacontext is not bound to an instance of the View Model. In the code behind after the InitializeComponent call, assign the datacontext such as:
InitializeComponent();
DataContext = new ShellViewModel();
I think you should use the RaisePropertyChanged in the ViewModel and Model and also should set the DataContext in the View.
<Window.DataContext>
<local:ShellViewModel />
</Window.DataContext>
You might want to consider using bind ObservableCollection to the datagrid. Then you don't need to maintain a private member _gridInventory and a public property GridInventory
//viewModel.cs
public ObservableCollection<ComputerRecord> GridInventory {get; private set;}
//view.xaml
<DataGrid ... ItemsSource="{Binding GridInventory}" .../>