I've created my own socket class and an instance of it in MainWindow.xaml.cs, and I want to create a small little TextBlock to monitor the connection status. I've been using this specific link: WPF textblock binding in XAML
Here's the code attempt. ComUplink.cs:
public class ComUplink
{
public String ConnectionStatus = "Idle";
public Socket Socklink;
}
In MainWindow.xaml.cs:
public partial class MainWindow : Window
{
ComUpLink Uplink;
...
public void Login_Click(object Sender, RoutedEventArgs e)
{
Uplink = new ComUpLink();
}
}
AND in the XAML file:
<TextBlock x:Name="textBlock3"
TextAlignment="Right"
HorizontalAlignment="Left"
Margin="12,218,0,0"
TextWrapping="Wrap"
Text="{Binding Path=Uplink.ConnectionString}"
VerticalAlignment="Top"
Foreground="#616161"
Width="236"/>
So, my question is, why isn't this binding properly? Am I missing an implementation of INotifyPropertyChanged?
Well you made three little mistakes:
You can only bind to properties (if those values change use INotifyPropertyChanged)
You need to set the DataContext
Your Binding used the wrong property name (ConnectionString instead of ConnectionStatus)
Try those modifications:
in MainWindow.xaml.cs:
public void Login_Click(object Sender, RoutedEventArgs e)
{
this.DataContext = new ComUpLink();
}
in ComUplink.cs:
public class ComUplink : INotifyPropertyChanged
{
private String connectionStatus = "Idle";
public String ConnectionStatus
{
get
{
return this.connectionStatus;
}
set
{
this.connectionStatus = value;
this.OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Socket Socklink;
}
in MainWindow.xaml:
<TextBlock x:Name="textBlock3"
TextAlignment="Right"
HorizontalAlignment="Left"
Margin="12,218,0,0"
TextWrapping="Wrap"
Text="{Binding Path=ConnectionStatus}"
VerticalAlignment="Top"
Foreground="#616161"
Width="236"/>
You first need to set the data context of the text block to be the main window or a property.
Second you need to bind to a public property not to field
Related
What am I doing wrong?
I have a Class Model.cs that has my DataContext
I have a Button and a TextBlock next to it. I have tried binding and implementing INotifyPropertyChanged.
When the button is clicked it calls a method that uses WinForms to look for a folder location and display it in the TextBlock
but it does not update. If I debug I get the path correctly.
Any help much appreciated.
MainWindow.xaml
<Button Name="projectLocationBtn"
Width="150"
Height="30"
Click="projectLocationBtn_Click">
<StackPanel Orientation="Horizontal">
<fa:FontAwesome Icon="FolderOpen" Margin="0 0 10 0" />
<TextBlock Text="Select Location" />
</StackPanel>
</Button>
<StackPanel Orientation="Horizontal" Margin="20 0 0 0">
<fa:FontAwesome Icon="AngleRight" Margin="0 0 10 0"/>
<TextBlock Width="800"
TextAlignment="Left"
TextWrapping="NoWrap"
Text="{Binding ProjectLocation}"/>
</StackPanel>
MainWindow.xaml.cs
using M = MercuryTemplateGenerator.Model;
public MainWindow()
{
InitializeComponent();
DataContext = new M.Model();
}
private void projectLocationBtn_Click(object sender, RoutedEventArgs e)
{
M.Model m = new M.Model();
m.GetLocation();
}
Model Class
using Winforms = System.Windows.Forms;
namespace MercuryTemplateGenerator.Model
{
public class Model: INotifyPropertyChanged
{
string _projectLocation;
string _projectName;
public Model() {}
public string ProjectName
{
get {
return _projectName; }
set {
_projectName = value;
OnPropertyChanged("ProjectName");
}
}
public string ProjectLocation
{
get {
return _projectLocation; }
set {
_projectLocation = value;
OnPropertyChanged("ProjectLocation");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(property));
}
public void GetLocation()
{
// get path to desktop
var startPath =
Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
Winforms.FolderBrowserDialog folderDialog = new
Winforms.FolderBrowserDialog();
folderDialog.ShowNewFolderButton = false;
folderDialog.SelectedPath = startPath;
Winforms.DialogResult pathResult = folderDialog.ShowDialog();
if (pathResult == Winforms.DialogResult.OK)
{
_projectLocation = folderDialog.SelectedPath;
}
}
}
}
Many thanks.
The mistake is you have one instance of Model for dataContext of the page and have another one instance you're calling inside projectLocationBtn_Click. If a view is bounded to dataContext it means it's special instance of class lays under view and view will get new data from there. You need to call GetLocation method on the same instance of Model. For example, you can save your first model to field.
_dataContext = new M.Model();
DataContext = _dataContext;
And then use this instance inside handler
private void projectLocationBtn_Click(object sender, RoutedEventArgs e)
{
_dataContext.GetLocation();
}
I can see that after all, it won't work because you don't call OnPropertyChanged("ProjectLocation").
For calling it you have to call setter of ProjectLocation property
Replace:
_projectLocation = folderDialog.SelectedPath;
with
ProjectLocation = folderDialog.SelectedPath;
And for your info: Check how can Button's click be bound to DataContext with Binding work inside XAML file.
https://www.codeproject.com/Articles/238657/How-to-use-Commands-in-WPF
In the GetLocation function you need to set the ProjectLocation property to raise the PropertyChanged event, if you set directly the _projectLocation private field the event won't be raised because it is inside the setter of the property
I have a ListView that is bound on an ObservableCollection.
<ListView Grid.Column="0" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0" Margin="5" Name="CustomerListView" ItemsSource="{Binding Customers}" SelectedItem="{Binding Path=CurrentCustomer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Margin="5,0,0,0" Text="{Binding LastName}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the same View i have some TextBoxes which are meant to edit the CurrentCustomer. I also have a save button. If you click this button the modifications of the CurrentCustomer should be saved. If the button "cancel" is pressed the modifications should be discarded.
<TextBox Name="CustomerSalutationTextBox" Grid.Column="1" Grid.Row="0" Height="20px" Margin="5" Text="{Binding Path=CurrentCustomer.Salutation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The Problem is, if i make some changes on the currentCusomer, they are taking effect immediately.
Do you have a solution?
What you need to add in your ViewModel / the class you have a binding context to is to save what was previous in the Textfield.
And when you hit abort, u just overwrite your newValue with the old one.
I'm going to setup a small example.
class ExampleViewModel : INotifyPropertyChanged {
private string _customerLastName;
private string _customerName;
private string _initialCustomerName;
private string _initialCustomerLastName;
public string CustomerName {
get { return this._customerName; }
set {
this._customerName = value;
this.OnPropertyChanged();
}
}
public string CustomerLastName {
get { return this._customerLastName; }
set {
this._customerLastName = value;
this.OnPropertyChanged();
}
}
public ExampleViewModel(string customerName, string customerLastName) {
this.CustomerName = customerName;
this.CustomerLastName = customerLastName;
this._initialCustomerName = customerName;
this._initialCustomerLastName = customerLastName;
}
//example event handler for your abort button
private void OnAbortButtonClick(object sender, EventArgs args) {
this.CustomerName = this._initialCustomerName; //set the initial name
this.CustomerLastName = this._initialCustomerLastName; //set the initial lastName
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Alternative
As you might load your data from a database/csv file/something else, you should know the original values. When pressing the cancel button, you could invoke a CancelButtonClicked event in your ViewModel and some other class which subscribed to the ViewModels event and knows the original Model could set the original values on that viewModel instance, or just exchange the ViewModel instance with the original one.
Have a look at : https://msdn.microsoft.com/en-us/library/hh848246.aspx
class ExampleViewModel : INotifyPropertyChanged {
private string _customerLastName;
private string _customerName;
public event CancelButtonClicked CancelButtonClicked;
public string CustomerName {
get { return this._customerName; }
set {
this._customerName = value;
this.OnPropertyChanged();
}
}
public string CustomerLastName {
get { return this._customerLastName; }
set {
this._customerLastName = value;
this.OnPropertyChanged();
}
}
public ExampleViewModel(string customerName, string customerLastName) {
this.CustomerName = customerName;
this.CustomerLastName = customerLastName;
}
private void OnAbortButtonClick(object sender, EventArgs args) {
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
internal delegate void CancelButtonClicked(object sender);
public class SomeOtherClass {
private ExampleViewModel _viewModel;
public SomeOtherClass() {
this._viewModel = new ExampleViewModel("foo", "bar");
this._viewModel.CancelButtonClicked += ViewModelOnCancelButtonClicked;
}
private void ViewModelOnCancelButtonClicked(object sender) {
ExampleViewModel vm = sender as ExampleViewModel;
vm.CustomerName = "foo"; //set the initial values again
vm.CustomerLastName = "bar";
}
}
Alternative2
You could also exchange the complete VM when the event of the cancel button is invoked to retreive its original state.
Alternative3
Everytime your SelectedItem changes, you could save the current state of it by creating a copy of it. When your CancelButton is pressed, you set the SelectedItem to the copy of your original viewModel.
You'd need a copy constructor or a copy method for that purpose.
I've found out another solution. In the code behind of the view i've added following:
void saveButton_Click(object sender, RoutedEventArgs e)
{
BindingExpression be = customerFirstNameTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
My textbox with UpdateSourceTrigger Explicit
<TextBox Name="customerFirstNameTextBox" Grid.Column="1" Grid.Row="2" Height="20px" Margin="5" Text="{Binding Path=CurrentCustomer.FirstName, Mode=TwoWay, UpdateSourceTrigger=Explicit}" IsEnabled="{Binding Path=IsCustomerTextEnabled}"/>
And my button
<Button Name="SaveButton" Click="saveButton_Click" Margin="5" Content="Save"/>
I have a Textbox in WPF which has its "Text" Property bound to a string "EmployeeSource.ID" with Mode=TwoWay. My problem is that when i change the EmployeeSource object, the binding does not work. What is wrong in my approach?
XAML
<TextBox x:Name="NameTextBox" Margin="5,5,10,5" TextWrapping="Wrap"
Text="{Binding SelectedEmployee.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" Grid.Column="1" />
Code Behind
private Employee _selectedEmployee;
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
_selectedEmployee = value;
UpdateTextBoxes();
}
}
private void UpdateTextBoxes()
{
NameTextBox.Text = SelectedEmployee?.Name;
}
Please try the code below. You need to implement the INotifyPropertyChanged interface inorder to achieve data binding in WPF. This is the basic concept of WPF data binding and MVVM pattern. This should work for you.
Code behind:
public class YourClassName : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Employee _selectedEmployee;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// The constructor is private to enforce the factory pattern.
private YourClassName()
{
_selectedEmployee = new Employee();
}
public Employee selectedEmployee
{
get
{
return this._selectedEmployee;
}
set
{
if (value != this._selectedEmployee)
{
this._selectedEmployee = value;
NotifyPropertyChanged("selectedEmployee");
}
}
}
}
XAML :
<TextBox x:Name="NameTextBox" Margin="5,5,10,5" TextWrapping="Wrap"
Text="{Binding selectedEmployee.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" Grid.Column="1" />
I have the following XAML:
<StackPanel Orientation="Vertical">
<TextBox IsReadOnly="True" Text="{Binding SchedulerStatus, Mode=OneWay}" Width="150" VerticalAlignment="Center" Margin="10" />
<Button Width="75" Height="30" Content="Test" Command="{Binding StartScheduler}" />
</StackPanel>
This is in a Window bound to this view model:
public class SchedulerViewModel : ViewModelBase // ViewModelBase implements INotifyPropertyChanged, using the [CallerMemberName] attribute.
{
private readonly SchedulerServiceClient _proxy;
public SchedulerViewModel()
{
_proxy = new SchedulerServiceClient();
SchedulerStatusPoller poller = new SchedulerStatusPoller(this, _proxy);
}
private SchedulerStatus _schedulerStatus;
internal SchedulerStatus SchedulerStatus
{
get
{
return _schedulerStatus;
}
set
{
if (value != _schedulerStatus)
{
_schedulerStatus = value;
OnPropertyChanged();
}
}
}
}
SchedulerServiceClient is a proxy to a WCF service that runs continually, and has a Status property that I need to watch. Because I cannot get callbacks from WCF to work after two solid days trying, I have implemented SchedulerStatusPoller, that periodically polls the WCF status, and updates the viewmodel status, in the hope that the display of the WCF status will be updated.
class SchedulerStatusPoller
{
private static readonly Timer StatusTimer = new Timer(5000);
private static SchedulerViewModel viewModel;
private static SchedulerServiceClient proxy;
public SchedulerStatusPoller(SchedulerViewModel targetViewModel, SchedulerServiceClient proxy)
{
SchedulerStatusPoller.proxy = proxy;
viewModel = targetViewModel;
StatusTimer.Elapsed += StatusTimerElapsed;
StatusTimer.AutoReset = true;
StatusTimer.Enabled = true;
StatusTimer.Start();
}
void StatusTimerElapsed(object sender, ElapsedEventArgs e)
{
viewModel.SchedulerStatus = proxy.GetStatus();
}
}
I have used the following code directly in the UI (the Window) that confirms that PropertyChanged is being raised by the SchedulerViewModel. The exception is thrown.
void _viewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
((SchedulerViewModel)DataContext).SchedulerStatus = SchedulerStatus.Processing;
}
The poller does call into the SchedulerStatus property on the viewmodel every five seconds, but the textbox does not update. What am I doing wrong?
You have to mention the source trigger
Text="{Binding SchedulerStatus, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Your OnPropertyChanged inside the Setter is missing an argument. Shouldn't it be sth. like
PropertyChanged("SchedulerStatus");
maybe,you can code it like this:
OnPropertyChanged(this,new PropertyChangedEventArgs("SchedulerStatus"));
I have MainWindow containing a datagrid and a "filter panel". The filter panel can change by a user input(button click). I try to achieve it with databinding. The problem that Im facing is the filter panel(which is a user control) is not loaded or refreshed.
Mainwindow xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250*" />
<ColumnDefinition Width="253*" />
</Grid.ColumnDefinitions>
<DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Margin="23,28,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="200" ItemsSource="{Binding OverviewableItems}" />
<UserControl Grid.Column="1" Content="{Binding UserControl}" DataContext="{Binding}" Grid.ColumnSpan="2" />
<Button Content="PersonFilter" Height="23" HorizontalAlignment="Left" Margin="23,268,0,0" Name="buttonPersonFilter" VerticalAlignment="Top" Width="75" Click="buttonPersonFilter_Click" />
<Button Content="ProjectFilter" Height="23" HorizontalAlignment="Left" Margin="132,268,0,0" Name="buttonProjectFilter" VerticalAlignment="Top" Width="75" Click="buttonProjectFilter_Click" />
</Grid>
code behind:
private ViewModel _viewModel;
public MainWindow()
{
_viewModel = new ViewModel(new DataProvider());
DataContext = _viewModel;
_viewModel.PropertyChanged += _viewModel.SetFilterType;
InitializeComponent();
}
private void buttonProjectFilter_Click(object sender, RoutedEventArgs e)
{
_viewModel.OverviewType = OverviewType.Project;
}
private void buttonPersonFilter_Click(object sender, RoutedEventArgs e)
{
_viewModel.OverviewType = OverviewType.Person;
}
First user control:
<Grid>
<DatePicker Grid.Column="1" Grid.Row="1" Height="25" HorizontalAlignment="Left" Margin="19,18,0,0" Name="datePickerFundingTo" VerticalAlignment="Top" Width="115" Text="{Binding ElementName=ProjectFilter, Path=FundingTo}" />
</Grid>
code behind for this user control is only this:
public DateTime FundingTo { get; set; }
public ProjectFilter()
{
FundingTo = DateTime.Now;
InitializeComponent();
}
Other user control: just simply contains a TextBox and a Button, for the sake of simplicity I didnt add any code behind to it.
ViewModel of the MainWindow:
public class ViewModel : INotifyPropertyChanged
{
private UserControl _userControl;
public UserControl UserControl
{
get { return _userControl; }
set
{
if (_userControl == value)
{
return;
}
OnPropertyChanged("UserControl");
_userControl = value;
}
}
private OverviewType _overviewType = OverviewType.None;
public OverviewType OverviewType
{
get { return _overviewType; }
set
{
if (_overviewType == value)
{
return;
}
OnPropertyChanged("OverviewType");
_overviewType = value;
}
}
private ObservableCollection<IOverviewItem> _overviewableItems;
public ObservableCollection<IOverviewItem> OverviewableItems
{
get { return _overviewableItems; }
set
{
if (_overviewableItems == value)
{
return;
}
_overviewableItems = value;
}
}
private readonly DataProvider _dataProvider;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(DataProvider dataProvider)
{
_dataProvider = dataProvider;
}
public void SetFilterType(object sender, EventArgs eventArgs)
{
switch (_overviewType)
{
case OverviewType.Project:
_userControl = new ProjectFilter();
break;
case OverviewType.Person:
_userControl = new PersonFilter();
break;
}
}
public void OnPropertyChanged(string name)
{
if (PropertyChanged == null)
return;
var eventArgs = new PropertyChangedEventArgs(name);
PropertyChanged(this, eventArgs);
}
}
plus I have an enum OverviewType with None,Project,Person values.
The property changed event fired properly, but the user control is not refreshed. Could anyone enlight me, where is the flaw in my solution?
And the other question I have, how can I communicate from the usercontrols to the mainwindow viewmodel? Forex: the datagrid should be changed according to its filter.
Any help would be greatly appreciated. Cheers!
There are different problems here.
As Clemens said, you must fire your event after the value is updated. But it's not the main issue here.
Second problem: you are affecting your new usercontrol to the private member, so you're totally bypassing your property.
Replace
_userControl = new ProjectFilter();
by
this.UserControl = new ProjectFilter();
Third problem, which is not directly related to your question but actually is your biggest problem: you have an architecture design issue. You're exposing in your viewmodel a UserControl, which is an anti-pattern. Your viewmodel must not know anything about the view, so it must NOT have any reference to the controls inside the view. Instead of the binding you wrote, you could fire an event from the viewmodel and add an event handler in your view so it's your view that updates the usercontrol.
Try to fire the PropertyChanged after changing a property's backing field:
public UserControl UserControl
{
get { return _userControl; }
set
{
if (_userControl != value)
{
_userControl = value; // first
OnPropertyChanged("UserControl"); // second
}
}
}
Similar for OverviewType.