Refresh textbox when variable changes - c#

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}"

Related

Property Changed not firing UWP

I'm trying to validate a property object with FluentValidation when that object changes.
private Empresa empresa { get; set; }
public Empresa Empresa{
get { return empresa; }
set {
if (empresa == value) return;
empresa = value;
RaisePropertyChanged("Empresa");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string empresa)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Empresa"));
}
private void EmpresaViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
CompanyValidator validator = new();
this.ValidationResult = validator.Validate(empresa);
}
But when the property changes in the XAML the set doesn't fire
<TextBox Header="Nome Simplificado"
MinWidth="200"
HorizontalAlignment="Left"
LostFocus="TextBox_LostFocus"
TextChanging="TextBox_TextChanging"
Text="{Binding Empresa.SimplifiedName, Mode=TwoWay,
UpdateSourceTrigger=Default}"
/>
Does anyone know how to get this working?
(Ps. When I instantiate a new object of that class the event fires).
Property Changed not firing UWP
The problem is that Text property binding path is Empresa's property, but not Empresa itself, you just implement RaisePropertyChanged for Empresa instance, So it only works when Empresa is created for the first time.
And if you want to update Text, you need also implement for SimplifiedName property.
public class Empresa:INotifyPropertyChanged
{
private string _simplifiedName;
public string SimplifiedName
{
get { return _simplifiedName; }
set
{
if (_simplifiedName != value)
{
_simplifiedName = value;
RaisePropertyChanged("SimplifiedName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Update Text
private void MyButton_Click(object sender, RoutedEventArgs e)
{
Empresa.SimplifiedName = "New Value";
}

WPF INotifyPropertyChanged without burning base class

I'm trying to find a simple approach for data binding in WPF.
I'm using the INotifyPropertyChanged interface and it works fine if it's implemented on an abstract base class and inherited by objects that have bound members.
public partial class MainWindow : Window
{
public static MainWindow Instance;
private readonly Vm _vm;
public MainWindow ()
{
InitializeComponent();
DataContext = _vm = new Vm
{
Button1 = new Vm.ObservableButton(button1, new List<string> { "Paused", "Logging" }, false),
Button2 = new Vm.ObservableToggleButton(button2, new List<string> { "Log All", "Log VBA" }, false),
};
}
private class Vm
{
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged ([CallerMemberName] string propName = "")
{
var pc = PropertyChanged;
if (pc != null)
pc(this, new PropertyChangedEventArgs(propName));
}
}
public class ObservableButton : ObservableObject
{
private readonly Button _b;
private readonly List<string> _options;
private string _content;
public string Content
{
get { return _content; }
set
{
if (_content == value) return;
_content = value;
OnPropertyChanged();
}
}
public Boolean On { set; private get; }
public ObservableButton (Button b, List<string> options, Boolean on = true)
{
_b = b;
_options = options;
_b.Click += Click;
On = on;
Content = On ? _options[0] : _options[1];
}
public void Click (object sender, RoutedEventArgs e)
{
On = !On;
Content = On ? _options[0] : _options[1];
}
}
public class ObservableToggleButton : ObservableObject
{
private readonly ToggleButton _b;
private readonly List<string> _options;
private string _content;
public string Content
{
get { return _content; }
private set
{
if (_content == value) return;
_content = value;
OnPropertyChanged();
}
}
private Boolean _on;
public Boolean On
{
private get { return _on; }
set
{
if (_on == value) return;
_on = value;
Content = value ? _options[0] : _options[1];
}
}
public ObservableToggleButton (ToggleButton b, List<string> options, Boolean on = true)
{
_b = b;
_options = options;
On = on;
Content = _b.IsChecked ?? false ? _options[0] : _options[1];
}
public void Push ()
{
var peer = new ToggleButtonAutomationPeer(_b);
var toggleProvider = peer.GetPattern(PatternInterface.Toggle) as IToggleProvider;
if (toggleProvider != null) toggleProvider.Toggle();
//On = !On;
}
}
public ObservableButton Button1 { get; set; }
public ObservableToggleButton Button2 { get; set; }
public Vm ()
{
}
}
}
<Grid Margin="0,0,183,134">
<Button x:Name="button1" Content="{Binding Button1.Content}" HorizontalAlignment="Left" Margin="112,134,0,0" VerticalAlignment="Top" Width="75"/>
<ToggleButton x:Name="button2" IsChecked="{Binding Button2.On, Mode=OneWayToSource}" Content="{Binding Button2.Content}" HorizontalAlignment="Left" Margin="206,134,0,0" VerticalAlignment="Top"/>
</Grid>
I wanted to try doing this without burning the base class though, so I implemented INotifyPropertyChanged on the View Model and routed the change events from the bound members, back through the single interface on the View Model. Even though the Binding Object has a reference to the Source and the correct property name, this fails silently.
I figured that it doesn't work because the Binding Object does some type checking, so I made a fake implementation on the bound properties and it works. Here is the code for that scenario...
public partial class MainWindow : Window
{
public static MainWindow Instance;
public MainWindow ()
{
InitializeComponent();
DataContext = new ViewModel
{
Button1 = new ViewModel.ObservableButton(button1, new List<string> { "Paused", "Logging" }, false),
Button2 = new ViewModel.ObservableToggleButton(button2, new List<string> { "Log All", "Log VBA" }, false),
};
}
public class ViewModel : INotifyPropertyChanged
{
private static ViewModel _instance;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged<T> (T control, [CallerMemberName] string propName = "")
{
var pc = PropertyChanged;
if (pc != null)
pc(control, new PropertyChangedEventArgs(propName));
}
public class ObservableButton : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged () {}
private readonly Button _b;
private readonly List<string> _options;
private string _content;
public string Content
{
get { return _content; }
set
{
if (_content == value) return;
_content = value;
_instance.OnPropertyChanged(this);
}
}
public Boolean On { set; private get; }
public ObservableButton (Button b, List<string> options, Boolean on = true)
{
_b = b;
_options = options;
_b.Click += Click;
On = on;
Content = On ? _options[0] : _options[1];
}
public void Click (object sender, RoutedEventArgs e)
{
On = !On;
Content = On ? _options[0] : _options[1];
}
}
public class ObservableToggleButton : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged () {}
private readonly ToggleButton _b;
private readonly List<string> _options;
private string _content;
public string Content
{
get { return _content; }
private set
{
if (_content == value) return;
_content = value;
_instance.OnPropertyChanged(this);
}
}
private Boolean _on;
public Boolean On
{
private get { return _on; }
set
{
if (_on == value) return;
_on = value;
Content = value ? _options[0] : _options[1];
}
}
public ObservableToggleButton (ToggleButton b, List<string> options, Boolean on = true)
{
_b = b;
_options = options;
On = on;
Content = _b.IsChecked ?? false ? _options[0] : _options[1];
}
}
public ObservableButton Button1 { get; set; }
public ObservableToggleButton Button2 { get; set; }
public ViewModel ()
{
_instance = this;
}
}
}
<Grid Margin="0,0,183,134">
<Button x:Name="button1" Content="{Binding Button1.Content}" HorizontalAlignment="Left" Margin="112,134,0,0" VerticalAlignment="Top" Width="75"/>
<ToggleButton x:Name="button2" IsChecked="{Binding Button2.On, Mode=OneWayToSource}" Content="{Binding Button2.Content}" HorizontalAlignment="Left" Margin="206,134,0,0" VerticalAlignment="Top"/>
</Grid>
So you can see that, even though the interface on the ObservableButton and ObservableToggleButton types are still routing the change notification through their parent, the Binding Object is happy because they toe the line on type.
Is there a good reason why the the child object needs to implement the interface even though there is already everything need to complete the binding without it?
I try to provide a clear example how this should be done in WPF instead of trying to fix the OP question.
XAML
<StackPanel>
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="bToV" />
</StackPanel.Resources>
<!--bind the text to the viewmodel content. Use a bool to visibilty converter to convert from true to Visible-->
<TextBlock
Text="{Binding Path=Content}"
Visibility="{Binding Path=IsContentVisible, Converter={StaticResource bToV}}" />
<!--Use a two way binding to sync the IsChecked property with the viewmodel-->
<ToggleButton IsChecked="{Binding Path=IsContentVisible,Mode=TwoWay}"
Content="{Binding Path=ToogleActionName}" />
</StackPanel>
code behind
to keep your project structure clear I warmly suggest to put each class in a separate file. However I put all 3 classes into one single file for easier posting.
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApplication4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ContentViewModel() { Content = "foo" };
}
}
public class ContentViewModel : ViewModelBase
{
private string _toogleActionName = "turn it off";
private bool _isContentVisible = true;
private string _content;
public bool IsContentVisible
{
get
{
return _isContentVisible;
}
set
{
_isContentVisible = value;
//switch action name
if (value)
ToogleActionName = "turn it off";
else
ToogleActionName = "turn it on";
OnPropertyChanged();
}
}
public string Content
{
get
{
return _content;
}
set
{
_content = value;
OnPropertyChanged();
}
}
public string ToogleActionName
{
get
{
return _toogleActionName;
}
set
{
_toogleActionName = value;
OnPropertyChanged();
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I hope this is showing how WPF is supposed to work with the MVVM pattern.
The problem there is that the ViewModel on the first example:
private class Vm
{
...
}
Does not implement INofityPropertyChanged interface, therefore whenever you say that you DataContext is "Vm", the binding would not know that a property has changed because the view model it is not implementing INotifyPropertyChanged...
And on the second example, it is working because you are implementing a INofityPropertyChanged on the view model class
public class ViewModel : INotifyPropertyChanged
{
...
}
Note that it doesn't matter if your child classes implements INotifyPropertyChanged if your base class doesn't implement it too and your base class is observing changes on the children and raises the changes as "its own"...

WPF TextBlock Binding to a String

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.

WPF MVVM textBox Text Binding

I am just getting started with MVVM so apologies if I've done something really stupid. I tried writing a very simple test to see if I could remember everything, and for the life of me I can't see why its not working.
In my view I have a textBox where its text property is bound to a value in the ViewModel. Then when pressing a button the value should be altered and the textBox update.
I can see the value does alter (I have added a MessageBox.Show() line in the buttom press command) however the textBox does not update.
I assume that this means I have not properly implemented the INotifyPropertyChanged event properly but am unable to see my mistake.
Could anyone point me in the right direction?
Here is the code:
View
<Window x:Class="Mvvm.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<TextBox Height="40" Width="200" Text="{Binding helloWorld.Message, UpdateSourceTrigger=PropertyChanged}"/>
<Button Command="{Binding UpdateTimeCommand}">Update</Button>
</StackPanel>
</Window>
Behind View
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel.MainWindowViewModel();
}
}
ViewModel
namespace Mvvm.ViewModel
{
internal class MainWindowViewModel
{
private HelloWorld _helloWorld;
/// <summary>
/// Creates a new instance of the ViewModel Class
/// </summary>
public MainWindowViewModel()
{
_helloWorld = new HelloWorld("The time is " + DateTime.Now.ToString("HH:mm:ss"));
UpdateTimeCommand = new Commands.UpdateTimeCommand(this);
}
/// <summary>
/// Gets the HellowWorld instance
/// </summary>
public HelloWorld helloWorld
{
get
{
return _helloWorld;
}
set
{
_helloWorld = value;
}
}
/// <summary>
/// Updates the time shown in the helloWorld
/// </summary>
public void UpdateTime()
{
helloWorld = new HelloWorld("The time is " + DateTime.Now.ToString("HH:mm:ss"));
}
public ICommand UpdateTimeCommand
{
get;
private set;
}
}
Model
namespace Mvvm.Model
{
class HelloWorld : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public HelloWorld(string helloWorldMessage)
{
Message = "Hello World! " + helloWorldMessage;
}
private string _Message;
public string Message
{
get
{
return _Message;
}
set
{
_Message = value;
OnPropertyChanged("Message");
}
}
private void OnPropertyChanged(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p));
}
}
}
}
Commands
namespace Mvvm.Commands
{
internal class UpdateTimeCommand : ICommand
{
private ViewModel.MainWindowViewModel _viewModel;
public UpdateTimeCommand(ViewModel.MainWindowViewModel viewModel)
{
_viewModel = viewModel;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_viewModel.UpdateTime();
}
}
}
Sorry for such a long post and it being a spot my mistake post but I've looked at it for so long and I don't know what I'm doing wrong
Thanks!
The Problem that you have is that you are changing the wrong Property. Instead of changing the HelloWorld.Message Property, you are changing MainWindowViewModel.HelloWorld property. Your code will work OK if you change this line:
public void UpdateTime()
{
helloWorld = new HelloWorld("The time is " + DateTime.Now.ToString("HH:mm:ss"));
}
For this one
public void UpdateTime()
{
helloWorld.Message = "The time is " + DateTime.Now.ToString("HH:mm:ss");
}
If you want to keep your original code, then you need to implement INotifyPropertyChanged for your ViewModel, and rise the event when you change helloWorld object.
Hope this helps
I think you need to implement PropertyChanged notification on your ViewModel. You are creating a new HelloWorld in the UpdateTime method, but the UI doesn't know it.
Edit
I have a ViewModel base class which I derive all of my ViewModels from. It implements INotifyPropertyChanged, and has references to my relay command classes, and some other common stuff. I recommend always having INotifyPropertyChanged implemented on the ViewModel. The ViewModel is there to expose data to the UI, and it cant do that for data that changes without that interface.
i think your ViewModel needs to implement INotifyPropertyChanged too,
or you can set the DataContext before you call InitializeComponents(), if you do that you should change your code to NOT create a new instance every update like Agustin Meriles said.
i think you mistake Model and VM: Model is MainWindowViewModel and VM is HelloWorld
In your VM (class HelloWorld ) you need use your model
So, your classes will look like:
using System.ComponentModel;
namespace WpfApplication1
{
public sealed class TextVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TextInfo _info;
public TextVM()
{
_info = new TextInfo();
}
public string MyText
{
get { return _info.MyText; }
set
{
_info.MyText = value;
OnPropertyChanged("MyText");
}
}
private void OnPropertyChanged(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p));
}
}
}
}
using System;
namespace WpfApplication1
{
public sealed class TextInfo
{
public TextInfo()
{
MyText = String.Empty;
}
public string MyText { get; set; }
}
}
inset inside your ICommands

Trace listener to write to a text box (WPF application)

For my WPF application I do logging to a text file using a TextWriterTraceListener. How can I also display the Trace output to a textbox?
I use this for C# winforms, should be easily adjustable to wpf
public class MyTraceListener : TraceListener
{
private TextBoxBase output;
public MyTraceListener(TextBoxBase output) {
this.Name = "Trace";
this.output = output;
}
public override void Write(string message) {
Action append = delegate() {
output.AppendText(string.Format("[{0}] ", DateTime.Now.ToString()));
output.AppendText(message);
};
if (output.InvokeRequired) {
output.BeginInvoke(append);
} else {
append();
}
}
public override void WriteLine(string message) {
Write(message + Environment.NewLine);
}
}
Use it like
TraceListener debugListener = new MyTraceListener (theTextBox);
Debug.Listeners.Add(debugListener);
Trace.Listeners.Add(debugListener);
Remember to Trace/Debug.Listeners.Remove(debugListener); when you don't need it anymore.
How about implementing a custom TraceListener that simply appends trace messages to a string? You then expose that string as a property, implement INotifyPropertyChanged and databind a TextBox control to that property.
Something like this:
public class MyTraceListener : TraceListener, INotifyPropertyChanged
{
private readonly StringBuilder builder;
public MyTraceListener()
{
this.builder = new StringBuilder();
}
public string Trace
{
get { return this.builder.ToString(); }
}
public override void Write(string message)
{
this.builder.Append(message);
this.OnPropertyChanged(new PropertyChangedEventArgs("Trace"));
}
public override void WriteLine(string message)
{
this.builder.AppendLine(message);
this.OnPropertyChanged(new PropertyChangedEventArgs("Trace"));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
You would need to add this TraceListener to the list of active listeners:
Trace.Listeners.Add(new MyTraceListener());
Below code is C#6.0 style of #Mark Seemann's code.
public class MyTraceListener : TraceListener, INotifyPropertyChanged
{
private readonly StringBuilder _builder;
public MyTraceListener()
{
_builder = new StringBuilder();
}
public string Trace => _builder.ToString();
public override void Write(string message)
{
_builder.Append(message);
OnPropertyChanged(new PropertyChangedEventArgs("Trace"));
}
public override void WriteLine(string message)
{
_builder.AppendLine(message);
OnPropertyChanged(new PropertyChangedEventArgs("Trace"));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
}
Assume that MainViewModel is root DataContext of MainWindow.xaml file. To apply MyTraceListener in MVVM manner, write below code in MainViewModel.cs.
private string _traceOutput;
private readonly MyTraceListener _trace = new MyTraceListener();
// Constructor
public MainViewModel() {
// ...your viewmodel initialization code.
// Add event handler in order to expose logs to MainViewModel.TraceOutput property.
WeakEventManager<INotifyPropertyChanged, PropertyChangedEventArgs>.AddHandler(_trace, "PropertyChanged", traceOnPropertyChanged);
Trace.Listeners.Add(_trace);
}
private void traceOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Trace")
TraceOutput = _trace.Trace;
}
public string TraceOutput
{
get { return _traceOutput; }
set {
_traceOutput = value;
RaisePropertyChanged(); // This method is from Mvvm-light.
}
}
In MainWindow.xaml, bind TraceOutput property to TextBox. If you want the TextBox to scroll to bottom along with accumulated logs, apply TextChanged event.
<TextBox x:Name="TextBoxLog" TextWrapping="Wrap" Text="{Binding TraceOutput}" VerticalScrollBarVisibility="Auto" AcceptsReturn="True" TextChanged="TextBoxLog_OnTextChanged" />
in code-behind of XAML file (MainWindow.xaml.cs), event handler is simply as below.
private void TextBoxLog_OnTextChanged(object sender, TextChangedEventArgs e)
{
TextBoxLog.ScrollToEnd();
}
you could append a custom Listener which updates the Textbox.Text property.
You therefore need to inherit from the abstract base class TraceListener and override one of the TraaceData, TraceEvent, TraceTransfer methods.

Categories