Bind bool to visibility property - c#

I've searched this question a lot. But, it seems no solutions are working for me. I don't get any errors but I've added a breakpoint to my IValueConverter. The breakpoint does not ever get triggered. Why is it not using my converter? All I want to do is use view model strategy for visibility binding of a UI element (in this case a checkbox). Any help is appreciated.
IValueConverter:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Test_Tool.Common
{
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language) => (bool)value ^ (parameter as string ?? string.Empty).Equals("Reverse") ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, string language) => (Visibility)value == Visibility.Visible ^ (parameter as string ?? string.Empty).Equals("Reverse");
}
}
XAML:
<Page
x:Class="Test_Tool.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Test_Tool"
xmlns:converter="using:Test_Tool.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<converter:BooleanToVisibilityConverter x:Key="cvt" />
</Grid.Resources>
<Pivot x:Name="rootPivot" Title="Test Tool" >
<PivotItem Header="Test Selection">
<StackPanel>
<CheckBox x:Name="dppCheckBox" Content="DPP" Margin="5,8,5,5" Visibility="{Binding IsDirect, Converter={StaticResource cvt}}" />
</StackPanel>
</PivotItem>
</Pivot>
</Grid>
</Page>
ViewModel:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Test_Tool.ViewModels
{
public class MainPageViewModel : INotifyPropertyChanged
{
//Localized private vars
private bool _isDirect;
//Public vars for bindings
public bool IsDirect
{
get
{
return _isDirect;
}
set
{
_isDirect = value;
OnPropertyChanged();
}
}
public MainPageViewModel()
{
//Any Initialization
IsDirect = false;
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName]string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage:
using DM_API_Test_Tool.ViewModels;
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace Test_Tool
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new MainPageViewModel();
}
}
}

Try this
namespace Test_Tool.ViewModels
{
public class MainPageViewModel : INotifyPropertyChanged
{
private bool _isDirect = false;
public bool IsDirect
{
get
{
return _isDirect;
}
set
{
set { SetField(ref _isDirect, value, "isDirect"); }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
}
this should work now.
However I prefer the BindableBase method more (save the content below in a new class and call it something like BindableBase.cs)
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Mvvm
{
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
and then your ViewModel would be reduced to
using Mvvm;
namespace Test_Tool
{
public class MainPageViewModel : BindableBase
{
private bool _isDirect = false;
public bool IsDirect
{
get { return _isDirect; }
set { SetProperty(ref _isDirect, value); }
}
}
}
and one last thing: In your MainPage.xaml you want to bind to rootPivot, so you call
rootPivot.DataContext = null;
rootPivot.DataContext = new MainPageViewModel();
or something like that.

You can also try using external convertor library.
I found this nuget package:click here
This library is very simple to use, first install the nuget package and insert:
<Window x:Class="WPFUI.Views.MainWindow"
...
xmlns:convertor="clr-namespace:suren37.convertor;assembly=suren37.convertor"
...>
as namespace at the top of the view and you can now bind a boolean value to the visibility as
<CheckBox Grid.Row="1" IsChecked="{Binding IsVisibile}" Grid.Column="1" Margin="8 0" />
<TextBlock Grid.Row="1" Text="This text becomes visible on checked"
Visibility="{Binding IsVisibile, Converter={convertor:BoolToVisibilityConverter}}"
Grid.Column="2" TextWrapping="WrapWithOverflow"/>
To check out the working sample visit the github page, here.

Related

How to take a WPF CommandParameter to the ViewModel in MVVM?

I hope somebody can help me out here. Simplified the code for posting.
We have a main window (MvvmTestView) with a menu, and a 2nd window (SettingsView) which holds several tabs. I can open the SettingsView window alright. I can even select which Tab to open by setting this in the code.
How can I get back the correct value with the command parameter from the XAML code so that the correct tab opens?
MvvmTestView.xaml:
<Window x:Class="MvvmTest.Views.MvvmTestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:MvvmTest.ViewModels"
WindowStartupLocation="CenterScreen"
Title="MvvmTestView"
Height="500"
Width="500">
<Window.DataContext>
<vm:MvvmTestViewModel/>
</Window.DataContext>
<Grid>
<DockPanel>
<Menu>
<MenuItem Header="Menu">
<MenuItem
Header="Tab01"
Command="{Binding SettingsViewCommand}"
CommandParameter="0"/>
<MenuItem
Header="Tab02"
Command="{Binding SettingsViewCommand}"
CommandParameter="1"/>
</MenuItem>
</Menu>
</DockPanel>
<DockPanel>
<Label Content="MainView" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</DockPanel>
</Grid>
</Window>
SettingView.xaml
<Window x:Class="MvvmTest.Views.SettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tabData="clr-namespace:MvvmTest.Views"
xmlns:vm="clr-namespace:MvvmTest.ViewModels"
WindowStartupLocation="CenterScreen"
Title="SettingsView"
Height="400"
Width="400">
<Window.DataContext>
<vm:MvvmTestViewModel/>
</Window.DataContext>
<Grid>
<TabControl
SelectedIndex="{Binding SettingsSelectedIndex, Mode=TwoWay}">
<tabData:Tab01View/>
<tabData:Tab02View/>
</TabControl>
</Grid>
</Window>
SettingsViewModel.cs
using System.ComponentModel;
namespace MvvmTest.ViewModels
{
public class SettingsViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
private int _settingsSelectedIndex;
public int SettingsSelectedIndex
{
get
{
return _settingsSelectedIndex;
}
set
{
_settingsSelectedIndex = value;
OnPropertyChanged("SettingsSelectedIndex");
}
}
}
}
MvvmTestViewModel.cs
using MvvmTest.Commands;
using MvvmTest.Views;
using System.ComponentModel;
using System.Windows.Input;
namespace MvvmTest.ViewModels
{
internal class MvvmTestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private SettingsViewModel SettingsViewModel;
public MvvmTestViewModel()
{
SettingsViewModel = new SettingsViewModel();
SettingsViewCommand = new SettingsViewCommand(this);
}
public ICommand SettingsViewCommand
{
get;
private set;
}
public void SettingsWindow()
{
SetIndex();
SettingsView settingsView = new SettingsView()
{
DataContext = SettingsViewModel
};
settingsView.ShowDialog();
}
public int SetIndex()
{
SettingsViewModel.SettingsSelectedIndex = 1;
return SettingsViewModel.SettingsSelectedIndex;
}
}
}
SettingsViewCommand.cs
using MvvmTest.ViewModels;
using System;
using System.Windows.Input;
namespace MvvmTest.Commands
{
internal class SettingsViewCommand : ICommand
{
private MvvmTestViewModel settingsViewModel;
public SettingsViewCommand(MvvmTestViewModel settingsViewModel)
{
this.settingsViewModel = settingsViewModel;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
settingsViewModel.SettingsWindow();
}
}
}
I suggest to avoid creating multiple command classes like SettingsViewCommand : ICommand. Instead use some general-purpose command class (e.g. RelayCommand from MvvmFoundation NuGet package)
assuming you added MvvmFoundation to your project, refactor MvvmTestViewModel class like this:
internal class MvvmTestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private SettingsViewModel SettingsViewModel;
public MvvmTestViewModel()
{
SettingsViewModel = new SettingsViewModel();
SettingsViewCommand = new RelayCommand<int>(SettingsWindow);
}
public ICommand SettingsViewCommand
{
get;
private set;
}
public void SettingsWindow(int index)
{
SettingsViewModel.SettingsSelectedIndex = index;
SettingsView settingsView = new SettingsView()
{
DataContext = SettingsViewModel
};
settingsView.ShowDialog();
}
}
CommandParameter from a view is passed to SettingsWindow method in a viewModel and used to change selected index

ComboBox does not update when ItemsSource changes

I hav a ComboBox that is bound to a List<string>. When the List changes, the ComboBox does not, even though PropertyChanged was raised. When debugging, I found out that the List Property is even read.
The error can be reproduced using the following code:
XAML
<Window x:Class="ComboBoxTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="90" Width="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding Source, Mode=OneWay}"/>
<Button Grid.Column="1" Content="add string" Command="{Binding}" CommandParameter="Add"/>
</Grid>
</Window>
Code behind
using System.Windows;
namespace ComboBoxTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
}
ViewModel
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
namespace ComboBoxTest
{
class ViewModel : INotifyPropertyChanged, ICommand
{
public ViewModel()
{
Source = new List<string>();
Source.Add("Test1");
Source.Add("Test2");
Source.Add("Test3");
}
private List<string> _Source;
public List<string> Source
{
get { return _Source; }
set
{
_Source = value;
OnPropertyChanged("Source");
}
}
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public bool CanExecute(object parameter)
{
return true;
}
public event System.EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if ((string)parameter == "Add")
{
Source.Add("New string");
OnPropertyChanged("Source");
}
}
}
}
Why isn't the ComboBox updating?
The ComboBox does not update because it doesn't see any changes when it checks the List. The reference stays the same and the ComboBox is not informed about changes inside the List.
Refactoring the code to use ObservableCollection instead of List will solve the problem, because ObservableCollection implements INotifyCollectionChanged, what is necessary to inform the View about Changes inside an Object.

WPF CommandParameter binding fails in ToolBar?

Here's the situation: It's a bit hard to describe, so skip to the steps to recreate and copy/paste the code into a new project if you want. ListViewModel contains a list of ViewModel (Items) and a list of ICommand (Actions). MainWindow has a ToolBar that binds to the Actions, a ListView that binds to the Items, and a ContextMenu that binds to the actions within the ListView. In my implementation of ICommand (Command.cs), I have added the ability to insert custom code (the OnIsVisible property) that checks if the command Visibility should be Visible or Collapsed. This code works great for the Actions in the ToolBar and ContextMenu until you open the ContextMenu. Then the CommandParameter for the ToolBar is forever null, except when the ContextMenu is open.
Steps to recreate:
Select an item in the ListView
Click "Show When Selected" in the ContextMenu
Select another item in the ListView
At this point, the CommandParameter binding will always be NULL to the command object. So the "Show When Selected" button in the ToolBar will no longer appear.
Code:
In a new WPF application project called "NullParameter", create/edit the following files...
MainWindow.xaml:
<Window x:Class="NullParameter.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">
<Window.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected"
Value="{Binding IsSelected}" />
</Style>
<Style x:Key="contextMenuItemStyle"
TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding Header}" />
<Setter Property="Command"
Value="{Binding}" />
<Setter Property="Visibility"
Value="{Binding Visibility}" />
<Setter Property="CommandParameter"
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.Tag.SelectedItems}" />
</Style>
<DataTemplate x:Key="toolBarActionItemTemplate">
<Button Content="{Binding Header}"
Command="{Binding}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ToolBar}, Path=Tag.SelectedItems}"
Visibility="{Binding Visibility}" />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Children>
<ToolBar Grid.Row="0"
ItemsSource="{Binding Actions}"
ItemTemplate="{StaticResource toolBarActionItemTemplate}"
Tag="{Binding}" />
<ListView Grid.Row="1"
ItemsSource="{Binding Items}"
SelectionMode="Extended"
Tag="{Binding}">
<ListView.ContextMenu>
<ContextMenu ItemsSource="{Binding Actions}"
ItemContainerStyle="{StaticResource contextMenuItemStyle}" />
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Header="Id"
DisplayMemberBinding="{Binding Id}"/>
</GridView>
</ListView.View>
</ListView>
</Grid.Children>
</Grid>
</Window>
CommandBase.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
namespace NullParameter
{
public abstract class CommandBase : ICommand, INotifyPropertyChanged
{
private Visibility _visibility;
private string _error;
public Visibility Visibility
{
get { return _visibility; }
protected set
{
if (_visibility == value)
return;
_visibility = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("Visibility"));
}
}
public string Error
{
get { return _error; }
set
{
if (_error == value)
return;
_error = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("Error"));
}
}
public bool CanExecute(object parameter)
{
Error = DoCanExecute(parameter);
Visibility = DoIsVisible(parameter) ? Visibility.Visible : Visibility.Collapsed;
return Error == null;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
DoExecute(parameter);
}
protected abstract string DoCanExecute(object parameter);
protected abstract bool DoIsVisible(object parameter);
protected abstract void DoExecute(object parameter);
public event PropertyChangedEventHandler PropertyChanged;
}
}
Command.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace NullParameter
{
public class Command : CommandBase
{
public Action OnExecute { get; set; }
public Func<bool> OnIsVisible { get; set; }
public Func<string> OnCanExecute { get; set; }
public string Header { get; set; }
protected override string DoCanExecute(object parameter)
{
if (OnCanExecute == null)
return null;
return OnCanExecute();
}
protected override bool DoIsVisible(object parameter)
{
if (OnIsVisible == null)
return true;
return OnIsVisible();
}
protected override void DoExecute(object parameter)
{
if (OnExecute == null)
return;
OnExecute();
}
}
public class Command<T> : CommandBase
where T : class
{
public Action<T> OnExecute { get; set; }
public Func<T, bool> OnIsVisible { get; set; }
public Func<T, string> OnCanExecute { get; set; }
public string Header { get; set; }
protected T Convert(object parameter)
{
if (parameter == null)
Console.WriteLine("NULL");
return parameter as T;
}
protected override string DoCanExecute(object parameter)
{
if (OnCanExecute == null)
return null;
var p = Convert(parameter);
if (p == null)
return "Invalid Parameter";
return OnCanExecute(p);
}
protected override bool DoIsVisible(object parameter)
{
if (OnIsVisible == null)
return true;
var p = Convert(parameter);
if (p == null)
return false;
return OnIsVisible(p);
}
protected override void DoExecute(object parameter)
{
if (OnExecute == null)
return;
var p = Convert(parameter);
if (p == null)
return;
OnExecute(p);
}
}
}
ListViewModel.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace NullParameter
{
public class ListViewModel
{
public IList<ViewModel> Items { get; private set; }
public IList SelectedItems { get; private set; }
public IList<ICommand> Actions { get; private set; }
public ListViewModel()
{
var items = new ObservableCollection<ViewModel>()
{
new ViewModel()
{
Id = 1
},
new ViewModel()
{
Id = 2
},
new ViewModel()
{
Id = 3
},
};
Items = items;
SelectedItems = items;
Actions = new List<ICommand>()
{
new Command()
{
OnExecute = ShowAlways,
Header = "Show Always"
},
new Command<IList<ViewModel>>()
{
OnExecute = ShowWhenSelected,
OnIsVisible = (list) => { return list.Count(o => o.IsSelected) > 0; },
Header = "Show When Selected"
}
};
}
public void ShowAlways()
{
Console.WriteLine("ShowAlways()");
}
public void ShowWhenSelected(IList<ViewModel> viewModels)
{
Console.WriteLine("ShowWhenSelected({0})", String.Join(",", viewModels.Where(o => o.IsSelected).Select(o => o.Id)));
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NullParameter
{
public class ViewModel : INotifyPropertyChanged
{
private bool _isSelected;
private int _id;
public event PropertyChangedEventHandler PropertyChanged;
public int Id
{
get { return _id; }
set
{
if (_id == value)
return;
_id = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("Id"));
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected == value)
return;
_isSelected = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
}
}
So after reading a few posts about how glitchy the CommandParameter DependencyProperty is, I've given up on using it entirely. Instead, I simply construct my Command objects by passing in the list of selected items in my ListViewModel. Then in the CanExecute and Execute method, I use the stored list of selected items instead of the .NET supplied parameter.
Although this provides a workable solution, it does not necessarily solve the problem posed by the initial question. So I will leave this here as a suggestion for anyone else unfortunate enough to have these issues.

WPF MVVM ContextMenu binding IsOpen to Model

I have a button with a context menu associated to it. I can right click on the button and show the context menu as you would expect, however I want to be able to show the context menu after another event, such as a left click, or a drag and drop style event.
I am attempting to do this by binding the IsOpen property of the context menu to the view model, but this is not working as expected. On first left click of the button, nothing happens, although I can see the property on the view model that IsOpen is bound to being updated correctly.
If I right click, the menu will display correctly, and after this if I left click the menu will also show.
Has anyone ever seen this or have any ideas on what I need to do to get the contextMenu to open when the IsOpen property is updated?
XAML
<Window x:Class="PopUpTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mvp="clr-namespace:PopUpTest"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Window.DataContext>
<mvp:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.Resources>
<ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" IsOpen="{Binding PopupViewModel.IsOpen, Mode=TwoWay}">
<MenuItem Header="Delete" />
</ContextMenu>
</Grid.Resources>
<Button Command="{Binding DisplayPopupCommand}" ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}"/>
</Grid>
Code Behind
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Practices.Prism.Commands;
namespace PopUpTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MainWindowViewModel : BaseViewModel
{
private readonly PopupViewModel<ChildViewModel> _popupViewModel;
private readonly DelegateCommand _displayPopupCommand;
public MainWindowViewModel()
{
_popupViewModel = new PopupViewModel<ChildViewModel>(new ChildViewModel { FirstName = "John", LastName = "Doe" });
_displayPopupCommand = new DelegateCommand(() => { PopupViewModel.IsOpen = PopupViewModel.IsOpen == false; Console.WriteLine(PopupViewModel.IsOpen); });
}
public ICommand DisplayPopupCommand
{
get { return _displayPopupCommand; }
}
public PopupViewModel<ChildViewModel> PopupViewModel
{
get { return _popupViewModel; }
}
}
public class PopupViewModel<T> : BaseViewModel
{
private readonly T _data;
public PopupViewModel(T data)
{
_data = data;
}
public T Data
{
get { return _data; }
}
private bool _isOpen;
public bool IsOpen
{
get { return _isOpen; }
set
{
if (_isOpen != value)
{
_isOpen = value;
OnPropertyChanged("IsOpen");
}
}
}
}
public class ChildViewModel : BaseViewModel
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName != value)
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
if (_lastName != value)
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have been able to solve this by introducing a BindingProxy to the XAML as described in the answer to this post on the MSDN forums:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/a4149979-6fcf-4240-a172-66122225d7bc/wpf-mvvm-contextmenu-binding-isopen-to-view-model?forum=wpf
The binding proxy gets around the issue where the ContextMenu does not have a DataContext until it first displays after a right click.
The issue is discussed further here:
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

IValueConverter.Convert doesn't get called on a OneWay bind

I have a boolean property (that does called INotifyPropertyChanged in the setter) that is bound to a button.IsEnabled property in my XAML. Currently I'm using a TwoWay binding, but this is causing problems and I only need a OneWay binding. My problem is that the converter I'm using doesn't get called beyond the first time the program starts up. I've put breakpoints in the setter and it gets called loads, but the Convert() method doesn't get called at all. Why is this?
Some code:
public bool IsSaving
{
get
{
return _isSaving;
}
set
{
_isSaving = value;
NotifyOfPropertyChange(() => IsSaving);
}
}
and the XAML:
IsEnabled="{Binding Path=IsSaving, Mode=OneWay, Converter={StaticResource booleanToNotEnabledConverter}}"
The converter really just returns !(bool)value so the button gets disabled when IsSaving is true.
Some changes at runtime might cause the binding to break (since you bind to the DataContext + a relative path), if you use Visual Studio make sure to check the Output-window for any binding errors.
Edit: Since it has not been noted: That is a stardard binding and there is nothing wrong with the posted code, the problem has to be caused by the context.
Here is the code I used and this works:
Converter:
using System.Windows.Data;
using System;
namespace SilverlightApplication1
{
public class BooleanToNotEnabledConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML:
<UserControl x:Class="SilverlightApplication1.MainPage"
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:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<local:BooleanToNotEnabledConverter x:Key="booleanToNotEnabledConverter" />
</UserControl.Resources>
<StackPanel Margin="100">
<Button Content="Flip"
Click="Button_Click" />
<TextBlock Text="{Binding IsSaving}"
Height="20" />
<Button IsEnabled="{Binding IsSaving, Mode=OneWay, Converter={StaticResource booleanToNotEnabledConverter}}"
Content="Some Button" />
</StackPanel>
</UserControl>
Code behind:
using System.Windows.Controls;
using System.Windows;
using System.ComponentModel;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
private Data _data;
public MainPage()
{
InitializeComponent();
_data = new Data { IsSaving = true };
this.DataContext = _data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_data.IsSaving = !_data.IsSaving;
}
}
public class Data : INotifyPropertyChanged
{
#region IsSaving Property
private bool _isSaving;
public bool IsSaving
{
get
{
return _isSaving;
}
set
{
if (_isSaving != value)
{
_isSaving = value;
OnPropertyChanged("IsSaving");
}
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var p = PropertyChanged;
if (p != null)
{
p(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Are you sure you invoke the PropertyChanged event handler with the correct string?
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsSaving"));

Categories