I would learn to use converter on Wpf(xaml).
<Window x:Class="TextExpanderGriglia.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:local="clr-namespace:TextExpanderGriglia"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</Window.Resources>
<Grid>
<Button Content="CIAO" Width="50" Height="50" Visibility="{Binding vButton,Converter={StaticResource BoolToVisibilityConverter}}"> </Button>
<Button Content="Cambia" Width="50" Height="50" Margin="56,134,411,135" Click="Button_Click"/>
</Grid>
This is my Xaml code.
Stupid example to start, I have 2 button and on button "Cambia" I set boolean value vButton = !vButton, but also if vButton is false the first button doesn't hide.
What is missing in my code?
This is my converter
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (Visibility)value == Visibility.Visible;
}
}
MainWindows.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool vButton;
public event PropertyChangedEventHandler PropertyChanged;
public bool VButton
{
get
{
return vButton;
}
set
{
if (value != vButton)
{
this.vButton = value;
NotifyPropertyChanged("VButton");
}
}
}
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
vButton = !vButton;
}
}
The converter looks fine.
Most likely scenario here is that the property you are binding to is not raising change notifications. For example:
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class MyModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _isButtonVisible;
public bool vButton
{
get { return _isButtonVisible; }
set
{
if (value == _isButtonVisible)
return;
_isButtonVisible = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
To set DataContext
In your constructor:
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
In MVVM pattern, you should define the ViewModel separately from your Windows.
For ex. in your case:
public class MainWindowViewModel : INotifyPropertyChanged
{
private bool vButton;
public event PropertyChangedEventHandler PropertyChanged;
public bool VButton
{
get
{
return vButton;
}
set
{
if (value != vButton)
{
this.vButton = value;
NotifyPropertyChanged("VButton");
}
}
}
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then in your MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
Related
Hi I am following this tutorial,http://blogs.u2u.be/diederik/post/2011/11/14/null.aspx, to bind the visibility of an element to a Boolean property. The program is not working. Here is the code:
<Page.Resources>
<local:BooleanToVisibilityConverter x:Key="TrueToVisibleConverter"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<TextBlock Text=" Hello World"
Visibility="{Binding Path=Show_element, Converter={StaticResource TrueToVisibleConverter}}"/>
<Button Click="Button_Click">press button</Button>
</StackPanel>
</Grid>
public sealed partial class MainPage : Page , INotifyPropertyChanged
{
private bool show_element ;
public bool Show_element
{
get { return show_element; }
set
{
show_element = value;
this.OnPropertyChanged();
Debug.WriteLine("Show_element value changed");
}
}
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Show_element = !Show_element;
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnPropertyChanged(string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class BooleanToVisibilityConverter : IValueConverter
{
public bool IsReversed { get; set; }
public object Convert(object value, Type typeName, object parameter, string language)
{
var val = System.Convert.ToBoolean(value);
if (this.IsReversed)
{
val = !val;
}
if (val)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
The visibility does not change with the property. I was having an error due to intellisense (Error Xaml namespace) which was resolved. Not sure what is wrong with this code.
Thank you.
change
this.OnPropertyChanged();
to
this.OnPropertyChanged("Show_element");
edit:
besides that, you don't have a ViewModel (sorry, missed that when I was checking your code), so you need to create one and set it as DataContext:
ViewModel.cs:
public class ViewModel : INotifyPropertyChanged
{
private bool show_element;
public bool Show_element
{
get { return show_element; }
set
{
show_element = value;
this.OnPropertyChanged("Show_element");
Debug.WriteLine("Show_element value changed");
}
}
public ViewModel()
{
}
public void ButtonClicked()
{
Show_element = !Show_element;
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnPropertyChanged(string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
and your MainPage.xaml.cs should look somehow like that:
public sealed partial class MainPage : Page
{
private ViewModel _viewModel;
public MainPage()
{
this.InitializeComponent();
_viewModel = new ViewModel();
DataContext = _viewModel;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_viewModel.ButtonClicked();
}
}
I am trying to learn WPF/MVVM and for educational reason I create a simple application. I have some issues trying to implement a Command Object.
When a button control is clicked I want the background color of the Grid change to yellow using a Command Object. There are a lot of stuff about how to do this, but I want to do it with the clean way. Generally I want to achieve a loose coupling between View, ViewModel and the Command Object in order to test those classes.
Also i do not want to use some Libraries like Prism because I have the need to fully understand MVVM first.
I have a code sample but of course it does not have functionality. Just represented it for convenience reason.
My view XAML
<Window x:Class="Calendar.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:local="clr-namespace:Calendar"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="480">
<Grid Background="{Binding BackgroundColour}" Margin="0,0,2,0">
<Button Margin="197,247,200,-239" Grid.Row="3" Grid.ColumnSpan="2" Command="{Binding SubmitCommand}">Color</Button>
</Grid>
My ModelView class
public class MainWindowViewModel : INotifyPropertyChanged {
//Command part
ICommand SubmitCommand;
public MainWindowViewModel(ICommand command) {
SubmitCommand = command;
}
//Data Binding part
public event PropertyChangedEventHandler PropertyChanged;
private Brush backgroundColour = (Brush)new BrushConverter().ConvertFromString("Red");
public Brush BackgroundColour {
get { return this.backgroundColour; }
set {
if (value != this.backgroundColour) {
this.backgroundColour = value;
var handler = this.PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs("BackgroundColour"));
}
}
}
(it also has a data binding part but it does not have to do with my issue)
You would like not to have anything related to windows like colors(Brushes or Brush) in the viewmodel. Refer my below code.
<Window x:Class="MVVMNav_Learning.Window1"
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:MVVMNav_Learning"
mc:Ignorable="d"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<local:ColorConverterConverter x:Key="ColorConverterConverter"></local:ColorConverterConverter>
</Window.Resources>
<Grid>
<Grid Background="{Binding BackgroundColour,Converter={StaticResource ColorConverterConverter}}" Margin="0,0,2,0">
<Button Margin="50" Command="{Binding SubmitCommand}">Color</Button>
</Grid>
</Grid>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel:INotifyPropertyChanged
{
private MyColor backColor;
public MyColor BackgroundColour
{
get { return backColor; }
set { backColor = value; OnPropertyChanged("BackgroundColour"); }
}
public ICommand SubmitCommand { get; set; }
public ViewModel()
{
BackgroundColour = MyColor.Red;
SubmitCommand = new BaseCommand(Execute);
}
public void Execute(object parameter)
{
BackgroundColour = MyColor.Yellow;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
public enum MyColor
{
Red,
Green,
Yellow
}
public class BaseCommand : ICommand
{
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
{
_method = method;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
public class ColorConverterConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
MyColor color = (MyColor)value;
switch (color)
{
case MyColor.Red:
return Brushes.Red;
case MyColor.Green:
return Brushes.Green;
case MyColor.Yellow:
return Brushes.Yellow;
default:
{
return Brushes.Red;
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You need to create a public Property for the ICommand SubmitCommand and you can use a private DelegateCommand in its getter/setter.
You are not very clearly stating your question, but I gamble it to be: How to configure the command parameter for the viewmodel's constructor to have it change the backgroundcolour?
Commands do their work by having them implement ICommand.Execute(Object) So basically you want to have the command object you pass to the constructor to have a method like:
void Execute(object parameter)
{
viewModel.BackGroundColor=Brushes.Yellow;
}
This is awkward: the command is passed from outside the viewmodel, but it must have a reference to it to change its back colour. You may want to rethink your design.
Moreover: for the databinding engine to see the SubmitChangedCommand it must be a property:
public ICommand SubmitChangesCommand {get;set;}
I can figure out how to bind a property to a textbox in the codebehind, but with my current application I need to bind to a property from a different class. Here's a simplified version of what I have:
<Window x:Class="Project1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox x:Name="Textbox1" Text="{Binding Class1.Class2.TextToBind, Mode=TwoWay}" Height="20" Width="75" Background="#FFE5E5E5"/>
</Grid>
Codebehind:
namespace Project1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Class1 = new Class1();
}
public Class1 Class1 { get; set; }
}
}
Class1:
namespace Project1
{
public class Class1
{
public Class1()
{
Class2 = new Class2();
}
public Class2 Class2 { get; set; }
}
}
Final class:
namespace Project1
{
public class Class2
{
public Class2()
{
}
private string textToBind;
public string TextToBind { get { return textToBind; } set { SetProperty(ref textToBind, value); } }
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "")
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
}
You have to set DataContext for your TextBox or for your Window
namespace Project1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Class1 = new Class1();
Class1.Class2.TextToBind = "Test";
this.DataContext = this;
}
public Class1 Class1 { get; set; }
}
}
Also you need to inherit Class2 from INotifyPropertyChanged:
public class Class2 : INotifyPropertyChanged
{
public Class2()
{
}
private string textToBind;
public string TextToBind { get { return textToBind; } set { SetProperty(ref textToBind, value); } }
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "")
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
If you want to bind your textbox to a property from a class other than the datacontext of the window, you need to set it explicitly
I tried this code and it worked
<Window x:Class="Project1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<!-- first add namespace of your project-->
xmlns:local="clr-namespace:Project1"
Title="Window1" Height="300" Width="300">
<!--second define your data context class as resource-->
<Window.Resources >
<local:Class2 x:Key="class2"></local:Class2>
</Window.Resources>
<Grid>
<TextBox x:Name="Textbox1" Text="{Binding TextToBind, Mode=TwoWay}" Height="20" Width="75" Background="#FFE5E5E5">
<!--third set the data context of the textbox Explicitly-->
<TextBox.DataContext>
<StaticResourceExtension ResourceKey="class2"/>
</TextBox.DataContext>
</TextBox>
</Grid>
</Window>
Please note : if you are going to set the property TextToBind programmatically and you want your UI to show the result, you have to implement INotifyPropertyChanged.
I need Model to notify ViewModel if any property is changed, because I need to collect the changed model instances in a collection for further processing, also to enable and disable command buttons in the viewmodel.
So I've used ModelBase abstract class and added HasChanges property which I can test against in the viewmodel and catch the changed models.But it is not working and I don't know what i'm missing.
public abstract class ModelBase : INotifyPropertyChanged
{
protected ModelBase()
{
}
private bool _hasChanges;
public bool HasChanges
{
get
{
return _hasChanges;
}
set
{
if (_hasChanges != value)
{
_hasChanges = value;
RaisePropertyChanged("HasChanges");
}
}
}
protected void RaisePropertyChanged(string propertyName)
{
HasChanges = true;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
The Model is wrapped inside the ViewModel and bound to the View which is a DataGrid:
private Model_selectedModel;
public Mode SelectedModel
{
get
{
return _selectedModel;
}
set
{
if (_selectedModel != value)
{
_selectedModel = value;
NotifyPropertyChanged("SelectedModel");
}
}
}
Thanks for valuable help.
I tested your class and it's okay. I think the point is a typo here:
private Model_selectedModel;
public Mode SelectedModel
{
get
{
return _selectedModel;
}
set
{
if (_selectedModel != value)
{
_selectedModel = value;
NotifyPropertyChanged("SelectedModel");
}
}
}
There should be RaisePropertyChanged instead of NotifyPropertyChanged.
Below it's my test:
XAML
<Window x:Class="TestUpdatePropertyChanged.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:TestUpdatePropertyChanged"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<this:TestViewModel />
</Window.DataContext>
<Grid>
<TextBox Width="100" Height="25" Text="{Binding Path=TestString, UpdateSourceTrigger=PropertyChanged}" />
<Button Width="100" Height="30" VerticalAlignment="Top" Content="Click" Click="Button_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var testData = this.DataContext as TestViewModel;
testData.TestString = "Yay, it's change!";
if (testData.HasChanges == true)
{
MessageBox.Show("It's Change!");
}
}
}
public class TestViewModel : ModelBase
{
private string _testString = "test";
public string TestString
{
get { return _testString; }
set
{
if (_testString != value)
{
_testString = value;
RaisePropertyChanged("TestString");
}
}
}
}
public abstract class ModelBase : INotifyPropertyChanged
{
protected ModelBase()
{
}
private bool _hasChanges;
public bool HasChanges
{
get
{
return _hasChanges;
}
set
{
if (_hasChanges != value)
{
_hasChanges = value;
RaisePropertyChanged("HasChanges");
}
}
}
protected void RaisePropertyChanged(string propertyName)
{
HasChanges = true;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
In my program I would like to disable a contentPresenter when my other contentPresenter gets focus. Each presenter is represented by a property located in my MainWindowViewModel. This is also where the IsEnabled property is located for both presenters.
Both contentPresenters are created with the following structure: UserControl -> ViewModel -> Data Model.
Right now I am trying to disable the necessary contentPresenter by changing the IsEnabled property in the main window's ViewModel from the code-behind of the contentPresenter that gets focus.
contentPresenter User Control code-behind:
public partial class EditBlockUC : UserControl
{
public EditBlockViewModel ViewModel { get { return DataContext as EditBlockViewModel; } }
public EditBlockUC()
{
InitializeComponent();
}
//Runs when the user control gets focus
private void UserControl_GotFocus(object sender, RoutedEventArgs e)
{
//This UserControl has access to MainWindowViewModel through
//it's own ViewModel, EditBlockViewModel
ViewModel.MainViewModel.LeftWidgetEnabled = false;
}
}
The line: ViewModel.MainViewModel.LeftWidgetEnabled = false; successfully changes the property in the Main window's view model, but the view is not affected. Can I fix this by finding a way to call NotifyPropertyChange()? If so, how would I do that?
If this is the completely wrong solution please let me know, and help me fix it.
Thank you
Update 1:
My complete base class:
public class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public virtual void NotifyPropertyChange<TProperty>(Expression<Func<TProperty>> property)
{
var lambda = (LambdaExpression)property;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = (UnaryExpression)lambda.Body;
memberExpression = (MemberExpression)unaryExpression.Operand;
}
else
memberExpression = (MemberExpression)lambda.Body;
OnPropertyChanged(memberExpression.Member.Name);
}
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;
}
}
Update 2:
My LeftWidgetEnabled property:
public bool LeftWidgetEnabled
{
get { return _leftWidgetEnabled; }
set { SetField(ref _leftWidgetEnabled, value, "LeftWidgetEnabled"); }
}
The LeftWidgetEnabled of your ViewModel.MainViewModel class must be like this:
private bool leftWidgetEnabled;
public bool LeftWidgetEnabled
{
get { return leftWidgetEnabled; }
set { SetField(ref leftWidgetEnabled, value, "LeftWidgetEnabled"); }
}
Also, your MainViewModel must implement INotifyPropertyChanged.
You're better off letting the MainViewModel inherit from a ViewModelBase and let ViewModelBase implement INotifyPropertyChanged.
public class MainViewModel : ViewModelBase
{
private bool leftWidgetEnabled;
public bool LeftWidgetEnabled
{
get { return leftWidgetEnabled; }
set { SetField(ref leftWidgetEnabled, value, "LeftWidgetEnabled"); }
}
}
public class ViewModelBase : INotifyPropertyChanged
{
// boiler-plate
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;
}
}
Update 1
Your ContentPresenter should then be bound like:
<ContentPresenter IsEnabled="{Binding Path=LeftWidgetEnabled}" />
while the DataContext of your UserControl (where the ContentPresenter is on) should be an instance of MainViewModel.
For instance:
<UserControl
x:Class="MyApplication.UserControl1"
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:viewModels="**PATH TO YOUR VIEWMODELS-ASSEMBLY**"
mc:Ignorable="d">
<UserControl.DataContext>
<viewModels:MainViewModel />
</UserControl.DataContext>
<ContentPresenter IsEnabled="{Binding Path=LeftWidgetEnabled}" />
</UserControl>
You implement INotifyPropertyChanged as below
class ViewModel : INotifyPropertyChanged
{
private bool leftWidgetEnabled;
public bool LeftWidgetEnabled
{
get
{
return leftWidgetEnabled;
}
set
{
leftWidgetEnabled=value
OnPropertyChanged("LeftWidgetEnabled");
}
}
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}