WPF TextBlock Binding to a String - c#

I want to bind a TextBlock to a string which takes its value from a txt file. The string is correctly filled but its contents are not displayed.
Class file:
public partial class JokesMessageBox : Window
{
public JokesMessageBox()
{
InitializeComponent();
}
public string Joke { get; set; }
public string path = "data/jokes.txt";
public void ReadFile(string path)
{
Joke = File.ReadAllText(path);
}
}
XAML:
<TextBlock HorizontalAlignment="Left" Margin="22,10,0,0"
TextWrapping="Wrap" Text="{Binding Joke}" VerticalAlignment="Top"
Height="60" Width="309"/>
EDIT:
In the MainWindow class:
private void btnJokesFirstScreen_Click_1(object sender, RoutedEventArgs e)
{
JokesMessageBox jkb = new JokesMessageBox();
jkb.Show();
jkb.ReadFile("data/jokes.txt");
}
I spent 3+ hours on google, youtube, MSDN, StackOverflow and still can't get it working. What am I missing?

If the you need to update the binding, the property Joke must be a DependencyProperty or the Windows must implement INotifyPropertyChanged interface.
On the view, the binding needs to know Source.
Example #1 (Using DependencyProperty):
public partial class JokesMessageBox : Window
{
public JokesMessageBox()
{
InitializeComponent();
ReadFile(Path); //example call
}
public string Joke
{
get { return (string)GetValue(JokeProperty); }
set { SetValue(JokeProperty, value); }
}
public static readonly DependencyProperty JokeProperty =
DependencyProperty.Register("Joke", typeof(string), typeof(JokesMessageBox), new PropertyMetadata(null));
public const string Path = "data/jokes.txt";
public void ReadFile(string path)
{
Joke = File.ReadAllText(path);
}
}
Example #2 (Using INotifyPropertyChanged interface):
public partial class JokesMessageBox : Window, INotifyPropertyChanged
{
public JokesMessageBox()
{
InitializeComponent();
ReadFile(Path); //example call
}
private string _joke;
public string Joke
{
get { return _joke; }
set
{
if (string.Equals(value, _joke))
return;
_joke = value;
OnPropertyChanged("Joke");
}
}
public const string Path = "data/jokes.txt";
public void ReadFile(string path)
{
Joke = File.ReadAllText(path);
}
//INotifyPropertyChanged members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And the view (XAML partial):
...
<TextBlock HorizontalAlignment="Left" Margin="22,10,0,0"
TextWrapping="Wrap"
Text="{Binding Joke,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"
VerticalAlignment="Top"
Height="60" Width="309"/>
...
I hope it helps.

When you read the contents of the file, you assign the read string to your Joke property:
Joke = File.ReadAllText(path);
The Text property of the TextBlock is indeed bound to that property (if you have properly set the data context):
Text="{Binding Joke}"
However, what is missing is that the binding cannot possibly have any idea that the property value has changed. You need to issue a notification about the property change.
There are two ways to do this that will be recognized by WPF bindings:
You declare your Joke property as a dependency property. This is based on some WPF infrastructure that automatically issues the change notifications.
You have your class implement the INotifyPropertyChanged interface. Here, you have to implement a simple interface with a PropertyChanged event, which you have to fire in your property setter while passing the name of the property as a string.

Your class is not implementing INotifyPropertyChanged interface. So when you change property Joke TextBlock is not updated. I would do something like this:
public partial class JokesMessageBox : Window, INotifyPropertyChanged
{
public JokesMessageBox()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public string Joke { get; set; }
public string path = "data/jokes.txt";
public void ReadFile(string path)
{
Joke = File.ReadAllText(path);
OnPropertyChanged("Joke");
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I would also suggest you to read about MVVM patern.

Related

Refresh textbox when variable changes

Currently, in order for my textboxes to update, i need to navigate away from my SettingsPage and then back into it to see the changes in the TextBoxes.
Would you be able to help with getting these TextBoxes to update when the globalvariable changes? I have looked into using INotifyPropertyChanged. Im just not sure how best to implement it
Here is the code i have currently. its very basic.
Settings page XAML
<Frame Background="{StaticResource CustomAcrylicDarkBackground}">
<StackPanel>
<TextBox Width="500" Header="File Name" IsReadOnly="True" Foreground="White" Text="{x:Bind TextBoxFileName}"/>
<TextBox Width="500" Header="File Location" IsReadOnly="True" Foreground="White" Text="{x:Bind TextBoxFilePath}"/>
</StackPanel>
</Frame>
Code Behind
using static BS.Data.GlobalVariableStorage;
namespace BS.Content_Pages
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SettingsPage : Page
{
public SettingsPage()
{
this.InitializeComponent();
}
public string TextBoxFilePath = GlobalVariables.FilePath;
public string TextBoxFileName = GlobalVariables.FileName;
}
}
}
GlobalVariablesStorage Class
namespace BS.Data
{
class GlobalVariableStorage
{
public static class GlobalVariables
{
public static string FilePath { get; set; }
public static string FileName { get; set; }
}
}
}
Save File Function within MainPage.XAML.cs (Parses the save name to GlobalVariableStorage)
public async void SaveButton_ClickAsync(object sender, RoutedEventArgs e)
{
SaveFileClass instance = new SaveFileClass();
IStorageFile file = await instance.SaveFileAsync();
if (file != null)
{
GlobalVariables.FileName = file.Name;
GlobalVariables.FilePath = file.Path;
// Debugging the output file paths
// Remember to REMOVE
Debug.WriteLine(GlobalVariables.FileName);
Debug.WriteLine(GlobalVariables.FilePath);
WriteFile.WriteFileData();
}
}
The main issue is here is that you somehow need to tell your view when to refresh the data-bound values. And for you to be able to do this you need to know when this happens.
In other words, the GlobalVariables class should raise an event whenever any property is set to a new value. It could for example raise the built-in PropertyChanged event:
public static class GlobalVariables
{
private static string _filePath;
public static string FilePath
{
get { return _filePath; }
set { _filePath = value; NotifyPropertyChanged(); }
}
private static string _fileName;
public static string FileName
{
get { return _fileName; }
set { _fileName = value; NotifyPropertyChanged(); }
}
public static event PropertyChangedEventHandler PropertyChanged;
private static void NotifyPropertyChanged([CallerMemberName]string propertyName = "") =>
PropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
In your view you could then subscribe to this event and raise another event that the view handles. You tell the view update a data-bound value by implementing the INotifyPropertyChanged interface and raise the PropertyChanged event for the property to be updated. Something like this:
public sealed partial class SettingsPage : Page, INotifyPropertyChanged
{
public SettingsPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
GlobalVariables.PropertyChanged += GlobalVariables_PropertyChanged;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
GlobalVariables.PropertyChanged -= GlobalVariables_PropertyChanged;
}
private void GlobalVariables_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(GlobalVariables.FilePath):
NotifyPropertyChanged(nameof(TextBoxFilePath));
break;
case nameof(GlobalVariables.FileName):
NotifyPropertyChanged(nameof(TextBoxFileName));
break;
}
}
public string TextBoxFilePath => GlobalVariables.FilePath;
public string TextBoxFileName => GlobalVariables.FileName;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
Also note that the default mode of x:Bind is OneTime, so you should explicitly set the Mode to OneWay in the view, e.g.:
Text="{x:Bind TextBoxFilePath, Mode=OneWay}"

Why notification for List<T> property doesn't work

Why rising INotifypPropertyChanged for List<T> property doesn't work?
Consider this MCVE:
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string property = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public class TextWrapper
{
public string Text { get; set; }
public override string ToString() => Text;
}
public class ViewModel : NotifyPropertyChanged
{
public List<string> List { get; } = new List<string>();
public TextWrapper Text { get; } = new TextWrapper();
public void AddToList(string text)
{
List.Add(text);
OnPropertyChanged(nameof(List));
}
public void ChangeText(string text)
{
Text.Text = text;
OnPropertyChanged(nameof(Text));
}
}
public partial class MainWindow : Window
{
readonly ViewModel _vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _vm;
}
}
xaml:
<TextBlock Text="{Binding Text}" />
<ListBox ItemsSource="{Binding List}" />
Calling _vm.ChangeText(...) will properly update TextBlock, while calling _vm.AddToList(...) doesn't update ListBox (it will stay empty). Why?
Please note: I know about ObservableCollection<T> and I know about two possible workarounds (add setter to List and set it to e.g. null first and then back or change DataContext/ItemsSource). I am just curious what is under roof makes List<T> more special than TextWrapper.
When a WPF Binding handles the PropertyChanged event, it does not update its target property unless the effective value it produces has actually changed.
So unless the List property value actually changes (which it doesn't when you add an element), calling
OnPropertyChanged(nameof(List));
has no effect.
Replace
public List<string> List { get; } = new List<string>();
by
public ObservableCollection<string> List { get; } = new ObservableCollection<string>();
and write the AddToList method like this:
public void AddToList(string text)
{
List.Add(text);
}
For your TextWrapper class: Since you directly bind to the TextWrapper instance, the Binding calls its overridden ToString() method and hence produces a different value whenever the TextWrapper's Text property has changed.

Can't execute binding command

I have a button like this:
<Button Content="Gönder" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="932,23,0,0" Height="25" Command="{Binding Path=SetTeamList}" CommandParameter="{Binding ElementName=UrlBox, Path=Text}"/>
And at the VM, i have a method
public void SetTeamList(string Url)
{
//Some things here
}
The solution is WinForms app, so i set DataContext like this:
var view = new dTeamMapperForm();
view.DataContext = new TeamMappingVM();
elementHost1.Child = view;
Nothing happens when i click the button, no error or something. I put break point to SetTeamList method and it's not executing on button click.
Edit: I have changed the whole VM, now it looks like:
class TeamMappingVM : INotifyPropertyChanged
{
public ObservableCollection<Team> TeamList { get; set; }
public ICommand SetTeamsCommand { get; internal set; }
private string _url;
public string Url
{
get { return _url; }
set
{
_url = value;
NotifyPropertyChanged("Url");
}
}
public void SetTeamList()
{
var mapper = new TeamMapper();
TeamList = new ObservableCollection<Team>(mapper.MapTeams(Url));
}
public bool CanParseTeams()
{
return !string.IsNullOrEmpty(Url);
}
public TeamMappingVM()
{
SetTeamsCommand = new RelayCommand(SetTeamList, CanParseTeams);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
The Command-Property of a Button expects you to Bind to an Property of type ICommand.
In your Case you tried to Bind to a method, which does not work.
Since you edited you post i will just post this as the answer:
XAML:
<Button Content="Gönder" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="932,23,0,0" Height="25" Command="{Binding Path=SetTeamsCommand }" CommandParameter="{Binding ElementName=UrlBox, Path=Text}"/>
class TeamMappingVM : INotifyPropertyChanged
{
public ObservableCollection<Team> TeamList { get; set; }
public ICommand SetTeamsCommand { get; internal set; }
private string _url;
public string Url
{
get { return _url; }
set
{
_url = value;
NotifyPropertyChanged("Url");
}
}
public void SetTeamList()
{
var mapper = new TeamMapper();
TeamList = new ObservableCollection<Team>(mapper.MapTeams(Url));
}
public bool CanParseTeams()
{
return !string.IsNullOrEmpty(Url);
}
public TeamMappingVM()
{
SetTeamsCommand = new RelayCommand(SetTeamList, CanParseTeams);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
As a minor side note. Which was not asked. Since you are probably Using Databinding for your URL Textbox you don't need to pass it into the method via CommandParameter, Since the URL Property of your ViewMOdel represents this textbox. You want to try to seperate the view from the logic. This is a very small issue and might not have any effect, but it sort of is a bad habit to fall into.
As Xeun pointed out, a Command is not a method but an object implementing the ICommand interface. A Command implementation look like this:
class MyCommand: ICommand
{
public bool CanExecute(object parameter)
{
return true; // if your command is "enabled" otherwhise return false
}
public void Execute(object parameter)
{
// do something usefull
}
}
In this sample you should add an instance of MyCommand to your ViewModel an
bind to it.
Please notice usually you dont code commands this way.
A command usually interact with your ViewModel (ie it invokes Model methods) and inside MyCommand you have not references to the ViewModel hosting it.
(You could create a Command which hold a reference to its ViewModel, but...) Usually inside a ViewModel you use a Relay command or a Delegate command (which are basically the same thing).

Updating UI based on property change in a view model

I'm new to WPF, so there's probably something basic I'm missing here. I have an application that looks like this:
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test Application" Height="647" Width="723" Background="#88B0FF">
<DockPanel Name="MainDock">
<Button DockPanel.Dock="Top" Margin="5,0,5,0" x:Name="PingButton" Click="PingButton_OnClick">Ping</Button>
<TextBox Text="{Binding Path=Output}" />
</DockPanel>
</Window>
The code-behind is like this:
public partial class MainWindow : Window
{
private Model _applicationModel = new Model();
public Model ApplicationModel {
get { return _applicationModel; }
set { _applicationModel = value; }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = ApplicationModel;
ApplicationModel.Output = "Not clicked";
}
private void PingButton_OnClick(object sender, RoutedEventArgs e)
{
ApplicationModel.Output = "Clicked";
}
}
I have a small class called Model that implements INotifyPropertyChanged.
public class Model : INotifyPropertyChanged
{
public string Output { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I run this application, and the text box displays the text "Not clicked". When I click the button, I would expect that the text would change. It does not. The "ApplicationModel" object is updated, and this is reflected in the DataContext; I have a breakpoint in the OnPropertyChanged() method, however, and it appears that it's never being called.
What am I doing wrong?
OnPropertyChanged() isn't being called because you're not calling it.
There is no special magic that wires up calls to OnPropertyChanged by itself, so you need to do it yourself.
Specifically, you should modify your Output property to call it when it changes (and it wouldn't hurt to do the same for your ApplicationModel property:
private string output;
public string Output
{
get { return output; }
set
{
if (output != value)
{
output = value;
OnPropertyChanged("Output");
}
}
}
If you're targeting .NET 4.5 you can utilize the CallerMemberName attribute to reduce boilerplate code; This article explains how to do so. Then you'll have something like this:
private string output;
public string Output
{
get { return output; }
set { SetProperty(ref output, value); }
}
If you're using .NET 4.0 or below, you can use expression trees, as described in this answer.

XAML Binding to property

I have check box in my XAML+C# Windows Store application. Also I have bool property: WindowsStoreTestApp.SessionData.RememberUser which is public and static.
I want check box's property IsChecked to be consistent (or binded, or mapped) to this bool property.
I tried this:
XAML
<CheckBox x:Name="chbRemember1" IsChecked="{Binding Mode=TwoWay}"/>
C#
chbRemember1.DataContext = SessionData.RememberUser;
Code for property:
namespace WindowsStoreTestApp
{
public class SessionData
{
public static bool RememberUser { get; set; }
}
}
But it doesn't seem to work. Can you help me?
<CheckBox x:Name="chbRemember1" IsChecked="{Binding Path=RememberUser, Mode=TwoWay}"/>
public class SessionData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
bool _rememberUser;
public bool RememberUser
{
get
{
return _rememberUser;
}
set
{
_rememberUser = value;
NotifyPropertyChanged("RememberUser");
}
}
}
You need to implement some form of change notification in order for your check box to be "aware" of any changes to the property. The best bet is to use one of the many MVVM frameworks out there, if not, implement INotifyPropertyChanged in your ViewModel.
Also, typically in WPF, we do not set the DataContext of individual controls but set the DataContext of the Window or User Control to a ViewModel...
Here is an example of a property with change notification through one of the MVVM frameworks:
private bool createTrigger;
public bool CreateTrigger
{
get { return createTrigger; }
set { createTrigger = value; NotifyPropertyChanged(m => m.CreateTrigger); }
}
As you can see a simple auto-implemented property cannot be used for data binding in WPF...
I'd recommend going through Data Binding Overview over on MSDN...
You cannot bind to static properties as static properties cannot raise the PropertyChanged event. You will, of course, need INotifyPropertyChanged. But that is not relevant with static properties. You simply cannot bind to static properties. (You can in WPF and Silverlight)
Try like this, note that the property is not static but the backing field is:
public class SessionData : INotifyPropertyChanged
{
private static bool _rememberUser;
public event PropertyChangedEventHandler PropertyChanged;
public bool RememberUser
{
get { return _rememberUser; }
set
{
_rememberUser = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
this.DataContext = new SessionData();
<CheckBox x:Name="chbRemember1" IsChecked="{Binding RememberUser, Mode=TwoWay}"/>

Categories