INotifyPropertyChanged when PropertyChanged - c#

I have:
<Page.Resources>
<data:PublishManager x:Key="pubManager"/>
</Page.Resources>
then in my textBlock i used this:
<TextBlock Grid.Row="2" Canvas.ZIndex="3" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Path=SocialStatus, Mode=TwoWay, Source={StaticResource pubManager}}"></TextBlock>
my class PublishManager look like this:
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _SocialStatus;
public string SocialStatus
{
get
{
return _SocialStatus;
}
set
{
_SocialStatus = value;
RaisePropertyChanged("SocialStatus");
}
}
why when i write in my method code something like this it's don't work for me?
SocialStatus = "StackOverflow";
Why my page with TextBlock don't refresh content?

The problem is that you are using static resource in your binding scenario. Static resources aren't monitored in case of property changes. Do you really need to use your PublishManager as page resource?
It would be better when an instance of PublishManager will be set as DataContext.
So firstly set Page.DataContext:
<Page.DataContext>
<data:PublishManager/>
</Page.DataContext>
And later bind to context property:
<TextBlock Grid.Row="2" Canvas.ZIndex="3" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding SocialStatus}"/>

Do not use the StaticResource here. They are used where resource value is not likely to change. Read below links:
StaticResource
using StaticResource
if the value of SocialStatus is always going to be StackOverflow then declare the static property with that value and your binding should work. else you have to create an object of the class and give it as a datacontext to the view.
Or just create a datacontext when required
<TextBlock Grid.Row="2" Canvas.ZIndex="3" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=SocialStatus, Mode=TwoWay}">
<TextBlock.DataContext>
<data:PublishManager/>
</TextBlock.DataContext>
</TextBlock>
this will work too.

https://msdn.microsoft.com/en-us/library/cc838207%28v=vs.95%29.aspx
binding to a static source works. You just have to make sure data: maps to right namespace. As you did not provide the complete Xaml. you might want to check this.
<UserControl x:Class="PublishManager.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:namespace-to-code">
Although best practice is to use the DataContext to make it more reusable. Like the answer of Patryk provided.

Related

Binding elements to values in WPF

I'm trying to bind a Textbox to a string defined in the .cs file using the followings:
Xaml Code:
<TextBox x:Name="textBox_Data" CaretBrush="DodgerBlue" Foreground="White" Text="{Binding Data}" HorizontalAlignment="Left" Height="22" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="123" SelectionChanged="textBox_Data_SelectionChanged"/>
Xaml.cs Code:
public string Data{get; set;}
But the string isn't updating...
Your class has to derive from INotifyPropertyChanged and you have to implement it in your property setter
Or more pleasant way: Install PropertyChanged.Fody from nuget. You can read more about it here: https://github.com/Fody/PropertyChanged
And keep in mind, not to use this.DataContext=this; when initializing your window, use binding as dovid suggests.
{Binding Data} refer the DataContext of current element (or to one of ancestors).
one way
for refer the xaml.xxx.cs you need refer the Window element, you can give him a name:
<Window x:Name="window" x:Class=...
and change the Binding to refer element name:
Text="{Binding Data, ElementName=window}"
Second way
you can also inject all window class to current DataContext:
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}" x:Class=...
Now you can leave the original expression:
Text="{Binding Data}"
Third way
You can also set DataContext from the code. do not change anything in xaml, and add in this line (DataContext = this;) at end of constructor:
public xyz() {
InitializeComponent();
//...
DataContext = this;
}

Button should open new ContentControl based on MVVM pattern in WPF C#

I wanted to create a project based on the MVVM pattern.
Unfortunately, I didn't quite get it right and now I have numerous subsequent errors.
MainWindow.xaml
<Window x:Class="MyProject.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:MyProject"
xmlns:viewmodels="clr-namespace:MyProject.ViewModels"
xmlns:views="clr-namespace:MyProject.View"
mc:Ignorable="d"
Height="571.4" Width="730">
<Window.Resources>
<DataTemplate x:Name="Window1ViewTemplate" DataType="{x:Type viewmodels:Window1ViewModel}">
<views:Window1View DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
// Grid Definition not included - not relevant
<DockPanel Background="Blue" Grid.Row="1" Grid.Column="0" Grid.RowSpan="3">
<StackPanel>
<Button Content="Window1"
Height="45"
Click="Window1_click"/>
</StackPanel>
</DockPanel>
<ContentControl Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="3" Grid.RowSpan="4" Content="{Binding}"/>
</Grid>
</Window>
MainWindow.xaml.cs
namespace MyProject
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window1_click(object sender, RoutedEventArgs e)
{
DataContext = new Window1ViewModel();
}
}
}
I have a window which I named Window1. This one is not quite relevant I guess.
Now the Click Event button opens the window correctly. But this does not fit the MVVM pattern.
This will start a new thread, which then leads to countless subsequent errors.
How can I adjust the button event so that the binding is correct again.
Some suggestions for you.
Create a MainViewModel class, and set the DataContext of MainWindow to an instance of this.
If possible, don't use event handlers in MVVM, especially not for button click events. Instead, in MainViewModel, create a property of type ICommand, and bind the button's Command property to it.
Depending on what (library) you're using to implement the MVVM pattern, you may already have an ICommand implementation, such as MVVMLight's RelayCommand. If not, you can use a basic ICommand implementation such as
public class BasicCommand: ICommand
{
private readonly Action _execute;
public Command(Action execute)
{
_execute = execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_execute?.Invoke();
}
public event EventHandler CanExecuteChanged;
}
In MainViewModel, create a property of type object ContentViewModel, and bind the ContenControl's Content property to it. This property should implement INotifyPropertyChanged as you're going to update it from within the ViewModel.
In the corresponding method for the command, set the ContentViewModel property to an appropriate value - e.g. a Window1ViewModel instance.
In the ContentControl resources, define a DataTemplate for each type you might want to display. This needs to be based on a something other than a Window - eg Grid or CustomControl or UserControl.
.
<ContentControl
Margin="8"
Content="{Binding SelectedItem}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:Window1ViewModel}">
<ctrl:Window1Display DataContext="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Window2ViewModel}">
<ctrl:Window2Display DataContext="{Binding}" />
</DataTemplate>
...
</ContentControl.Resources>
</ContentControl>
More details, and a working example on my blog post.

How to make a TextBox listen to INotifyPropertyChanged

I googled for hours, but did not find a solution I was able to understand (I'm very new to OOP and C#...).
The task at hand is this:
I have a class "DutyDay" which has the property "ActualShowTime" which is a DateTime object.
a TextBox named "textBoxLegalFDP".
a method "CalculateLegalFDP" in which the "ActualShowTime" will be taken trough a series of calculations based on various other variables.
Now, whenever the "ActualShowTime" property in the class "DutyDay" has been changed, the TextBox "textBoxLegalFDP" should listen to that change, take the updated value of "ActualShowTime", send it trough "CalculateLegalFDP" and then put the new calculated result in the "textBoxLegalFDP.Text" property to be shown in the TextBox.
This is what I've done so far:
namespace DutyTimeRechner
{
public class DutyDay : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private DateTime _actualShowTime;
public DateTime ActualShowTime
{
get
{
return _actualShowTime;
}
set
{
if (!_actualShowTime.Equals(value))
{
_actualShowTime = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ActualShowTime"));
}
}
}
}
I hope this was correct so far, according to what I found on the internet.
I did'nt include the methode "CalculateLegalFDP" since it is not of the essence here.
What kind of code do I have to write, to make the TextBox listen to the change of the property and hand it to "CalculateLegalFDP" and the update it's own "Text" property?
I was thinking about something simple like:
textBoxLegalFDP.AddHandler.OnPropertyChanged = PropertyChangedEventHandler(object sender, "ActualShowTime");
But already by looking at it myself I know its crap, and Visual Studio was even less happy with that approach, but just so you know what kind of solution I have in mind!
PS: Does my thinking actually make sense? Or is there a better way to solve all of this?
Edit: here's the XAML code, after I put the DataContext in it (only the relevan parts):
<Window x:Class="DutyTimeRechner.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DutyTimeRechner"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Tour Details" Height="450" Width="1000" ResizeMode="NoResize">
<Window.Resources>
<local:DutyDay x:Key="DutyTimeRechner" />
</Window.Resources>
<StackPanel DataContext="{StaticResource DutyTimeRechner}" Orientation="Horizontal">
and:
<TextBox x:Name="textBoxActualFDP" Text="{Binding ActualShowTime, UpdateSourceTrigger=PropertyChanged}" Margin="2" Height="26" FontSize="16" TextAlignment="Center" MaxWidth="50" Width="43"/>
But it didn't work, it just gives me the standard date value "01.01.0001 12:00:00 AM" in the Textbox, what am I doing wrong?
You need a ValueConverter
What you need to do is bind your TextBox to the value as you have done
<TextBox Text="{Binding ActualShowTime, UpdateSourceTrigger=PropertyChanged}"...
But us a ValueConverter to change the value that is displayed. It would look something like this:
public class CalculateLegalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
// convert the value in your viewmodel for display
return CalculateLegalFDP(value);
}
......
Then wire up this converter to your binding:
1) add as a resource:
<Page.Resources>
<local:CalculateLegalConverter x:Key="LegalConverter"/>
</Page.Resources>
2) Add is to your binding expression:
<TextBox Text="{Binding ActualShowTime, UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource LegalConverter}}"...
Read about WPF MVVM :).
To solve your problem you need to add binding to your text box control
<TextBox Text="{Binding ActualShowTime, UpdateSourceTrigger=PropertyChanged}"></TextBox>
But since type you are trying to bind with text box is a datetime, you probably need to write converter to convert datetime to string and string to datetime

Listbox bound to ObservableCollection empty

I have the following xaml:
<Window x:Class="Retail_Utilities.Dialogs.AdjustPriceDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner" Name="Adjust_Price"
Title="Adjust Price" Background="#ee0e1c64" AllowsTransparency="True" WindowStyle="None" Height="330" Width="570" KeyDown="Window_KeyDown" Loaded="Window_Loaded">
<Grid Height="300" Width="550">
<ListBox HorizontalAlignment="Right" Margin="0,110,35,60" Name="lstReasons" Width="120" VerticalAlignment="Stretch"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window, AncestorLevel=1}, Path=reasons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=POS_Price_Change_Reason}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Here is the relevant c#:
namespace Retail_Utilities.Dialogs
{
public partial class AdjustPriceDialog : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Twr_POS_Price_Change_Reason> reasons; ...
and finally, here is the code from another page that opens this window:
AdjustPriceDialog apd = new AdjustPriceDialog();
apd.Owner = (Window)this.Parent;
apd.reasons = new ObservableCollection<Twr_POS_Price_Change_Reason>();
var pcr = from pc in ctx.Twr_POS_Price_Change_Reasons where pc.Deactivated_On == null select pc;
foreach (Twr_POS_Price_Change_Reason pc in pcr)
{
apd.reasons.Add(pc);
}
apd.AdjustingDetail = (Twr_POS_Invoice_Detail)lstDetails.SelectedItem;
if (apd.ShowDialog() == true)
{
}
When the dialog box opens, my lstReasons list is empty. I don't get any errors and when I place a stop in the code, I see that the reasons collection gets populated with the items from the table.
Reasons needs to be a Property (add { get; set;} ). Also, look at Visual Studio Output - it shows Binding errors, there should be some info about failed binding to reasons.
The problem seems to be How you are creating the property.
I know you put your prperty as an observable collection but this doesn't mean it is by it self observalble!
so you need to notify the UI when this property is changed by doing something in the setter like this:
public ObservableCollection<Twr_POS_Price_Change_Reason> reasons
{
get{....}
set
{
Notify('reasons')
}
}
I don't remember the exact code because I didn't use WPF for a while but it is a method in INotifyPropertyChanged, good luck!
It seems your binding path is set to POS_Price_Change_Reason, while the name of your property is reasons. Unless you didn't include POS_Price_Change_Reason in your example code and reasons is the backing field for this property.
Also, keep in mind that you can only bind to public properties, not fields. Additionally, if you change the value of the property, you need to notify the view of this change, by invoking your PropertyChangedEventHandler event for that property:
PropertyChanged(new PropertyChangedEventArgs("YourPropertyName"));

WPF View-ModelView Binding Need Help Please

I have been playing around and looking around on how to Bind a modelview to a view, but i cant seem to work it out.
I have a view called Search and I want to bind it to SearchModelView.
View has one button and one textbox and looks:
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,40,0,0" Name="comboBox1" VerticalAlignment="Top" Width="174" />
<Label Content="Client:" Height="28" HorizontalAlignment="Left" Margin="0,12,0,0" Name="label1" VerticalAlignment="Top" Width="71" />
<Label Content="Client Reference:" Height="28" HorizontalAlignment="Left" Margin="0,69,0,0" Name="label2" VerticalAlignment="Top" Width="117" />
<TextBox
x:Name="clientRefTxt"
Text="{Binding Path=ClientRef, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Height="23"
HorizontalAlignment="Left"
Margin="12,103,0,0"
VerticalAlignment="Top"
Width="174" />
<Button
Content="Search Debtors"
Height="23"
HorizontalAlignment="Left"
Margin="12,140,0,0"
Name="button1"
VerticalAlignment="Top"
Width="89"
Command="{Binding Path=SearchCommand}"/>
</Grid>
And I want it to bind to SearchViewModel:
namespace Master.ViewModel
{
public class SearchViewModel:WorkspaceViewModel
{
RelayCommand _searchCommand;
readonly Search _search;
#region Search Properties
public string ClientRef
{
get { MessageBox.Show("GET CLIENTREF"); return _search.ClientRef; }
set
{
MessageBox.Show("SET CLIENTREF");
if (value == _search.ClientRef)
return;
_search.ClientRef = value;
base.OnPropertyChanged("ClientRef");
}
}
#endregion
public ICommand SearchCommand
{
get
{
MessageBox.Show("SEARCHCOMMAND");
if (_searchCommand == null)
{
_searchCommand = new RelayCommand(
param=> this.Search(),
param=> this.CanSearch
);
}
return _searchCommand;
}
}
public void Search()
{
MessageBox.Show("SEARCHING");
}
bool CanSearch
{
get { return true; }
}
}
}
I removed all the assemblies at the top but assume that they are all there. Also note that SearchViewModel is in a separate dll, not in the exe with the View.
Any help would be great or at least a pointer in the write direction, I have already read the msdn article on MVVM and that didnt help...I kinda need a better rundown on binding those too pieces.
Thanks in Advance.
P.S.
Some more details:
SearchViewModel belongs to Master.ViewModel
SearchView is part of GUI.View
I have and idea how the binded objects work, im not to sure on how to bind the view to the viewmodel
Is your View a Grid? I've only used UserControl or Window types as Views, but you may have success using a Grid.
Regardless, this is the cleanest way to instantiate the ViewModel with a UserControl View. Just replace the UserControl tags with Grid tags if you're using a Grid.
<UserControl ...(blah blah)
xmlns:viewmodel="clr-namespace:Master.ViewModel">
<UserControl.DataContext>
<viewmodel:SearchViewModel/>
</UserControl.DataContext>
I believe keeping out of the View's code unless necessary is the preferred pattern for MVVM - let the XAML wire things up for you when possible.
You need to set the view's DataContext to an instance of the view model. There are a variety of ways of doing this, including frameworks that wire it up automagically, but the easiest way to get started is to do it in the constructor of the view:
partial class Search : Window
{
public Search()
{
InitializeComponent(); // provided by Visual Studio
DataContext = new SearchViewModel(); // all-important!
}
}
Obviously you may need to provide other information to initialise the SearchViewModel but hopefully this is enough to get you on the right track.
Your will need to bootstrap your application like #itowlson suggests.
But if you have more than one ViewModel you should allow WPF to do it for you. The basic way to do this (which is easy to maintain until you start having more than a dozen views) is to create a DataTemplate to tie the View with your ModelView(which most people call ViewModel).
So the xaml you provided is probably in a UserControl(at least it should be) so you need to do several things
First create a ResourceDictionary
(fast way is to right-click your project and click Add -> Resource Dictionary
In that file(let's name it Resources.xaml) put this :
<DataTemplate DataType="{x:Type vm:SearchViewModel}">
<vw:SearchView>
</DataTemplate>
The above is assuming you put the namespaces vw and vm for View and ViewModel namespaces respectively
Go to your App.xaml and put this:
<Application.Resources>
<ResourceDictionary Source="Resources.xaml"/>
</Application.Resources>
The above will tell WPF that whenever it encounters an object of type SearchViewModel to:
Instantiate a SearchView object
Set it's DataContext to the SearchViewModel object
HTH

Categories