I am trying to move from WinForms to WPF, and am stuck on binding.
I have a label:
<Label Name="labelState" Content="{Binding state}" HorizontalAlignment="Right" Margin="10,10,10,10" FontSize="12" />
In the cs of the same userControl (named FormInput), I have :
public string state { get; set; }
public FormInput()
{
state = "ok";
InitializeComponent();
}
Why doesn't this work?
Thank you.
When you are binding something in WPF you need to use INotifyPropertyChanged
Implement a class follows,
class TestObject : INotifyPropertyChanged
{
private string _state;
public string State
{
get
{
return _state;
}
set
{
if (_state == value) return;
_state = value;
OnPropertyChanged("State");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
and in your FormInput
public FormInput()
{
InitializeComponent();
TestObject t = new TestObject();
labelState.DataContext = t;
t.State = "ok";
}
and XAML as follows,
<Label Name="labelState" Content="{Binding State}" HorizontalAlignment="Right" >
Related
My ListBox doesn't react to my ObservableCollection.
This is the ListBox I am talking about.
<ListBox x:Name="CreateFieldsList"
HorizontalAlignment="Left"
Height="218"
VerticalAlignment="Top"
Width="244"
Margin="0,86,0,0"
BorderBrush="#FFB9B9B9">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="4"
Width="215"
Height="32.96"
Background="White">
<TextBlock Text="{Binding Name}"
FontWeight="Normal"
FontSize="18.667"
Padding="8,3,0,0" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my MainWindow, I prepare the data binding like this
private ObservableCollection<FieldListItem> _fieldItems;
public MainWindow()
{
InitializeComponent();
_fieldItems = new ObservableCollection<FieldListItem>();
CreateFieldsList.ItemSource = _fieldItems;
}
A FieldListItem is following
public class FieldListItem : ViewItem
{
private Field _field;
public string Name
{
get { return _field.Name; }
}
public string Value
{
get { return _field.Value; }
}
public FieldListItem(Field f)
{
_field = f;
}
}
and finally the ViewItem
public class ViewItem : INotifyPropertyChanged
{
private event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
//The interface forces me to implement this. Why?
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { }
remove { }
}
}
I don't know why this isn't working. Could you please help?
The INotifyPropertyChanged interface needs you to implement an event. Your event implementation does not work because registrations and deregistrations are ignored because the add and remove blocks are empty.
Implement the event without add and remove:
public class ViewItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string caller = "")
{
var copy = PropertyChanged;
if (copy != null)
copy(this, new PropertyChangedEventArgs(caller));
}
}
I want to accomplish a simple task.
Need to implement textbox lostfocus, As the user puts in data, as soon as one field is filled and he reaches on to the next, it should fire a validation function on the previous field.
Also, I am using MVVM pattern.
So I have this class
public class data : INotifyPropertyChanged
{
public string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public string firstname;
public string FirstName
{
get
{
return firstname;
}
set
{
firstname = value;
OnPropertyChanged("FirstName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Raise the PropertyChanged event
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In the Viewmodel I got this
data1 = new data() { name = "Eddie Vedder", firstname = "Eddie" }; //this line in initialization
public data _data1;
public data data1
{
get { return _data1; }
set
{
_data1 = value;
ValidateThis();
NotifyPropertyChanged(new PropertyChangedEventArgs("data1"));
}
}
In Xaml:
<StackPanel Orientation="Horizontal" >
<Label Width="90" Content="Name" Height="28" HorizontalAlignment="Left" Name="lblName" VerticalAlignment="Top" />
<TextBox Text="{Binding Path=data1.name, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" MaxLength="40" TabIndex="2" Height="25" Margin="0,3,0,0" HorizontalAlignment="Left" Name="txtName" VerticalAlignment="Top" Width="200" />
</StackPanel>
<StackPanel Orientation="Horizontal" >
<Label Width="90" Content="First Name" Height="28" HorizontalAlignment="Left" Name="lblFirstName" VerticalAlignment="Top" />
<TextBox Text="{Binding Path=data1.firstname, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" MaxLength="40" TabIndex="3" Name="txtFirstName" Height="25" Margin="0,3,0,0" VerticalAlignment="Top" Width="200" >
</TextBox>
</StackPanel>
My binding is working as it shoes the default name Eddie Vedder when I execute it.
When I debug it, it doesn't enter the class data.
As you use MVVM pattern I assume that you have some binding to view model property and it looks like:
Xaml:
<StackPanel>
<!--Pay attention on UpdateSourceTrigger-->
<TextBox Text="{Binding Text, UpdateSourceTrigger=LostFocus}" />
<TextBox />
</StackPanel>
c#:
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
Validate(); // Desired validation
OnPropertyChanged();
}
}
If you set UpdateSourceTrigger to LostFocus, property changed will be fired when you lost focus.
There is a very nice article for this: MVVM WPF commands
First create a class: the DelegateCommand.cs
public class DelegateCommand<T> : System.Windows.Input.ICommand where T : class
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public DelegateCommand(Action<T> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
return true;
return _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
Add the delegate into your ViewModel:
private readonly DelegateCommand<string> _lostFocusCommand;
public DelegateCommand<string> LostFocusCommand
{
get { return _lostFocusCommand; }
}
private string _input;
public string Input
{
get { return _input; }
set
{
_input = value;
}
}
And initialize it in the constructor of the ViewModel:
// _input will be the property you have with a binding to the textbox control in the view.
// in the canExecute part add the conditions you want to use to check if the lostfocus command will be raised
_lostFocusCommand = new DelegateCommand<string>(
(s) => { /* perform some action */
MessageBox.Show("The lostfocuscommand works!");
}, //Execute
(s) => { return !string.IsNullOrEmpty(_input); } //CanExecute
);
View:
you need to add the following namespace
xmlns:b="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And the control
<TextBox Grid.Column="0"
Text="{Binding Input, UpdateSourceTrigger=PropertyChanged}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="LostFocus">
<b:InvokeCommandAction Command="{Binding LostFocusCommand}" CommandParameter="{Binding Input}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
</TextBox>
Well this kinda did the trick
public class Validate
{
public static ErrorProperties ep = new ErrorProperties();
public static bool ValidateThis(string PropertyName, string PropertyValue)
{
if (PropertyValue.Length > 10)
{
ep.ErrorPropertyName = PropertyName;
return true;
}
return false;
}
}
public class ErrorProperties
{
public string ErrorPropertyName { get; set; }
public string Error { get; set; }
}
public class data : INotifyPropertyChanged
{
private ObservableCollection<ErrorProperties> _ErrorList = new ObservableCollection<ErrorProperties>();
public ObservableCollection<ErrorProperties> ErrorList
{
get
{
return _ErrorList;
}
set
{
if (_ErrorList != value)
{
_ErrorList = value;
OnPropertyChanged("ErrorList");
}
}
}
private string _Name;
public string Name
{
get
{
return _Name;
}
set
{
if (_Name != value)
{
_Name = value;
if (Validate.ValidateThis("Name", value))
ErrorList.Add(Validate.ep);
OnPropertyChanged("Name");
}
}
}
private string _FirstName;
public string FirstName
{
get
{
return _FirstName;
}
set
{
if (_FirstName != value)
{
_FirstName = value;
if (Validate.ValidateThis("FirstName", value))
ErrorList.Add(Validate.ep);
OnPropertyChanged("FirstName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Raise the PropertyChanged event
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have a simple object (which is globally initiated in App.xaml.cs):
public class now_playing : INotifyPropertyChanged
{
// notify
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string p)
{
Debug.WriteLine(p + ": notify propertychanged");
PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
// artist
public string artist
{
get
{
return _artist;
}
set
{
_artist = value;
NotifyPropertyChanged("artist");
}
}
private string _artist;
// album
public string album
{
get
{
return _album;
}
set
{
_album = value;
NotifyPropertyChanged("album");
}
}
private string _album;
// track title
public string tracktitle
{
get
{
return _tracktitle;
}
set
{
_tracktitle = value;
NotifyPropertyChanged("tracktitle");
}
}
private string _tracktitle;
}
Whenever I change the values, the class does notify (I see the debug).
So I guess the problems lies in my XAML or the code behind.
Page code:
public sealed partial class nowplaying : Page
{
// artistdata
public string artist { get { return App.nowplaying.artist; } }
// albumdata
public string album { get { return App.nowplaying.album; } }
// trackdata
public string tracktitle { get { return App.nowplaying.tracktitle; } }
public nowplaying()
{
this.InitializeComponent();
this.DataContext = this;
}
}
XAML:
<Grid Margin="50">
<TextBlock Text="{Binding tracktitle}" Foreground="White" FontSize="40"/>
<TextBlock Foreground="#dcdcdc" FontSize="20" Margin="0,50,0,0">
<Run Text="{Binding artist}"/>
<Run Text=" - "/>
<Run Text="{Binding album}"/>
</TextBlock>
</Grid>
Why does the UI not update when I change values?
Stack trace:
Music.exe!Music.App.InitializeComponent.AnonymousMethod__6(object sender = {Music.App}, Windows.UI.Xaml.UnhandledExceptionEventArgs e = {Windows.UI.Xaml.UnhandledExceptionEventArgs}) Line 50 C#
Music.exe!play_music.MessageReceivedFromBackground(object sender = null, Windows.Media.Playback.MediaPlayerDataReceivedEventArgs e = {Windows.Media.Playback.MediaPlayerDataReceivedEventArgs}) Line 57 C#
UPDATE: problem solved! I had to use a dispatcher when calling the propertychanged event:
CoreDispatcher dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
if (PropertyChanged != null)
{
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.PropertyChanged(this, new PropertyChangedEventArgs(p));
});
}
You "loose" the change notification in the properties in the Page as these properties do not have any change notifiaction.
Try using now_playing directly:
public sealed partial class nowplaying : Page
{
public now_playing NowPlaying { get { return App.nowplaying; } }
public nowplaying()
{
this.InitializeComponent();
this.DataContext = this;
}
}
and
<Run Text="{Binding NowPlaying.artist}"/>
Otherwise you need to implement INotifiyPropertyChanged in nowplaying and forward the events from now_playing.
You actually binding to artist, album and tracktitle Of nowplaying class which does implement INotifyPropertyChanged
I'm trying to bind a string property to show in my status bar if my database is connected. Here's the code:
C#
public class TimeBase : INotifyPropertyChanged
{
private DXTickDB db;
string[] args = new string[] { };
public event PropertyChangedEventHandler PropertyChanged;
private bool isTBconnected;
public string connectionStatus { get; set; }
public bool tb_isconnected
{
get { return isTBconnected; }
set
{
if (value != isTBconnected)
{
isTBconnected = value;
if(isTBconnected == false)
{
connectionStatus = "TimeBase is not connected";
}
else
{
connectionStatus = "TimeBase is connected";
}
OnPropertyChanged("connectionStatus");
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#region TimeBase Connection
public void ConnectToTimeBase()
{
if (args.Length == 0)
args = new string[] { "not available for security reasons" };
db = TickDBFactory.createFromUrl(args[0]);
try
{
db.open(true);
tb_isconnected = true;
}
catch
{
tb_isconnected = false;
}
}
#endregion
This is the Xaml for the status bar in my main window:
<StatusBar Height="23" DockPanel.Dock="Bottom" Background="Green">
<StatusBarItem>
<StackPanel Orientation="Horizontal">
<TextBlock
Foreground="{StaticResource Foreground}"
Text="{Binding Path=connectionStatus}">
</TextBlock>
</StackPanel>
</StatusBarItem>
</StatusBar>
I'm trying to bind it to the string property connectionStatus but no text appears even though when I debug it I can see connectionStatus updated. Any suggestions to what's wrong here?
DataContext property should contain your model like so:
TimeBase timeBaseInstance;
public MainWindow()
{
timeBaseInstance = new TimeBase();
//Set the dataContext so bindings can iteract with your data
DataContext = timeBaseInstance;
InitializeComponent();
}
How do I get the text bound to txtMessage from the second view model? When I had only one view model, the text was working fine. It does not work anymore when I moved the actual download code to second view model. Am I missing something? Any help appreciated.
Xaml:
<DockPanel DockPanel.Dock="Top">
<TextBlock x:Name="txtMessage" DockPanel.Dock="Top" Margin="5" Text="{Binding viewModel1.Message}" />
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="5,5">
<ProgressBar Width="300" Visibility="{Binding IsDownloading, Converter={converter:VisibilityConverter}}" IsIndeterminate="True" />
<Button Content="Cancel" />
</StackPanel>
</DockPanel>
<Button Content="Download" Width="120" Margin="0,0,5,0" Name="btnSubmit" Click="btnSubmit_Click" />
CodeBehind:
public partial class DownloadWindow: Window
{
DownloadWindowViewModel viewModel = new DownloadWindowViewModel();
public DownloadWindow()
{
InitializeComponent();
this.DataContext = viewModel;
}
private void btnSubmit_Click(object sender, RoutedEventArgs e)
{
viewModel.IsDownloading = true;
viewModel.Download();
}
}
viewModel:
public class DownloadWindowViewModel: INotifyPropertyChanged
{
Thread downloadThread;
public DownloadViewModel viewModel1;
public DownloadWindowViewModel()
{
viewModel1 = new DownloadViewModel();
}
private bool _isDownloading; = false;
public bool IsDownloading
{
get
{
return _isDownloading;
}
set
{
_isDownloading; = value;
OnPropertyChanged("IsDownloading");
}
}
public void Download()
{
viewModel1.Download();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
viewModel1:
public class DownloadViewModel: INotifyPropertyChanged
{
Thread _thread;
public void Download()
{
ThreadStart threadStart = delegate()
{
StartDownload();
};
_thread = new Thread(threadStart);
_thread.IsBackground = true;
_thread.Start();
}
private void StartDownload()
{
for (int i = 10; i < 1500; i++)
{
Thread.Sleep(5000);
Message = "Downloading " + i.ToString();
}
}
private string _message = "";
public string Message
{
get
{
return _message;
}
set
{
if (_message != value)
{
_message = value;
OnPropertyChanged("Message");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Your viewModel1 has to be a property, and it's a field at the moment. Change it to:
public DownloadViewModel viewModel1 { get; set; }
Explanation why such restriction exists, can be found here (primarily due to notification/verifications mechanisms simply not working for fields):
Why does WPF support binding to properties of an object, but not fields?