WPF Text Binding to non-static property in singleton class? - c#

I have a preloader screen user control with dynamic text that is bound to a PreloaderContent property within a singleton class. Singleton, because I want to just have a single instance of this property and change it easily within my application. The class is a singleton so I could easily implement INotifyPropertyChanged into the class to update the UI when the property value changes.
This method of binding below reflects the initial property value. However, whenever I change the property via accessing the singleton instance, the change is not reflected..
<TextBlock Panel.ZIndex="100" Margin="0" TextWrapping="Wrap" Text="{Binding PreloaderContent, Source={x:Static models:Loader.LoaderManager}}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="21" FontWeight="Bold" Foreground="#FFF">
Preloader Singleton
{
//TO-DO: Make this a singleton class to implement iNotifyPropertyChanged
//TO-DO: Also, potentially move this into a different directory?
public class Loader : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private static Loader _LoaderManager = new Loader();
public static Loader LoaderManager
{
get { return _LoaderManager; }
}
// Visbility parameter to determine visbility of custom preloader user control
private Visibility _preloader;
public Visibility Preloader
{
get
{
return _preloader;
}
set
{
_preloader = value;
if (_preloader != value)
{
_preloader = value;
NotifyPropertyChanged();
}
}
}
// Textual content showing preloader message inside preloader user control
private string _preloaderContent;
public string PreloaderContent {
get
{
return _preloaderContent;
}
set
{
_preloaderContent = value;
if (_preloaderContent != value)
{
_preloaderContent = value;
NotifyPropertyChanged();
}
}
}
}
}
XAML Code
Now, I want to bind the Text="" to the property of PreloaderContent (Which exists in another class, not the viewmodel), but I am having issues getting it to actually reflect the changes in the UI when the value changes.
<Grid>
<Border Panel.ZIndex="1000" d:IsHidden="True" Background="#80000000" Margin="0,0,0,-0.4">
<Grid>
<TextBlock Panel.ZIndex="100" Margin="0" TextWrapping="Wrap" Text="" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="21" FontWeight="Bold" Foreground="#FFF">
</TextBlock>
<TextBlock Panel.ZIndex="100" Margin="11,136,12,75.2" TextWrapping="Wrap" Text="Please Wait..." HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="14" FontWeight="Bold" Foreground="#FFF"/>
</Grid>
</Border>

Either the other class must also be static, or your Loader class must have a property that the other class can set which is reflected in the text box.

Rookie mistake. I was overriding the preloader value in my Content setter
_preloader = value;

Related

WPF Navigate through child elements or keep track of dynamically created child elements

I have a TabControl where I create tabs dynamically. I am finding it difficult to change the title of the TabItem.
<TabControl Name="AttorneysTabControl" Grid.Column="2" Grid.Row="0">
<TabControl.Resources>
<DataTemplate x:Key="AttorneyTabHeader">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Names}" Margin="2,0,0,0" FontSize="16" VerticalAlignment="Center" />
<Button Width="Auto" UseLayoutRounding="False" BorderBrush="Transparent" Background="Transparent" Click="CloseAttorneysTabButtonClick">
<Image Source="/images/close-cross-thin-circular-button/close-cross-thin-circular-button16.png" Height="16"></Image>
</Button>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="AttorneyTabContent">
<local:AttorneyDetails></local:AttorneyDetails>
</DataTemplate>
</TabControl.Resources>
For each TabItem I set a HeaderTemplate from the TabControl.Resources like this;
newTabItem.HeaderTemplate = (System.Windows.DataTemplate)AttorneysTabControl.FindResource("AttorneyTabHeader");
But I don't know how to change the contents of the TabItem header once the template has been set. I have tried using DataContext for the TabItem if that's the way to do it but it did not work, so that I could just use Binding in the template. That will be a lot easier.
You should normally write (first line is your unchanged code):
newTabItem.HeaderTemplate = (System.Windows.DataTemplate)AttorneysTabControl.FindResource("AttorneyTabHeader");
var tabItemData = new TabItemData() { Name="Initial name"} ;
newTabItem.DataContext = tabItemData;
And then once you need to update the tab header:
tabItemData.Name = "New name".
If that didn't work, that'd probably because your TabItemData.Name property doesn't notify of its changes. So make sure that your TabItemData class implements INotifyPropertyChanged and that the Name property notifies. Example:
public class TabItemData : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
if (value != this.name)
{
this.name= value;
NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In case you're lost I suggest reading the Managing data in a WPF application chapter of my Learn WPF MVVM book.

Binding not updating as expected

I have a pre-loader screen that essentially says "please wait" as I have server-side computation being processed for several seconds.
I have a value converter that should update and get rid of the loader screen once the server-side computation has been processed and stored.
WPF Portion
<Window.Resources>
<Client:BoolToVisibilityConverter x:Key="loadConverter"/>
</Window.Resources>
.
.
.
<Border Panel.ZIndex="1000" BorderBrush="Yellow" BorderThickness="1" Visibility="{Binding OverlayVisibility, Converter={StaticResource loadConverter}, Mode=TwoWay}" Background="#80000000" Margin="0,0,0,-25.6">
<Grid>
<TextBlock Panel.ZIndex="100" Margin="0" TextWrapping="Wrap" Text="Loading Passive Seismic Nodes..." HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="21" FontWeight="Bold" Foreground="#FFF"/>
<TextBlock Panel.ZIndex="100" Margin="11,136,12,75.2" TextWrapping="Wrap" Text="Please Wait..." HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="14" FontWeight="Bold" Foreground="#FFF"/>
</Grid>
</Border
I have an OverlayVisibility property in this class that is a boolean value to help toggle the preloader screen.
Portion of the Client Class
public void LoadRoles()
{
foreach (var roleName in ChefServer.GetCookbookNames())
{
Cookbooks.Add(new Cookbook() { CookbookName = roleName });
}
//This isn't making the preloader disappear
uiContext.Send((_ => { overlayVisibility = false; }), null);
Console.WriteLine("Done!"); //This gets called successfully
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
//This function gets called when WPF form loads
public void Loader()
{
uiContext = SynchronizationContext.Current; //Declared at top in namespace
OverlayVisibility = true; //Make preloader screen show at boot
}
#region Props
private bool overlayVisibility;
public bool OverlayVisibility
{
get { return overlayVisibility; }
set
{
overlayVisibility = value;
OnPropertyChanged("OverlayVisibility");
}
}
#endregion
You're setting overlayVisibility (the field), not OverlayVisibility (the property).
Therefore, you never actually raise PropertyChanged, and WPF never finds out.
Are you sure you have set up DataContext correctly? try adding the following line to your c'tor if you have not set it up yet
this.DataContext = this;

UI (XAML) is not updating the properties But propertyChanged triggred in ViewModel

I have been facing a issue in updating the XAML in windows phone 8... the properties are binded in XAML with the viewModel, propertyChange is triggered and it changes the values of the properties. but the property members in XAML are only updated once at the beginning since then it does not update any thing in XAML... Although the properties continue to change in ViewModel.... the properties belong to a LIST of observation collection and finally Observation Collection is binded to LongListSelector
I have changed the binding Mode to "two Way" but useless i have pasted the code below.
Looking forward for help.
ViewModel:
private string _description;
public string description
{
set
{
_description = value;
RaisePropertyChanged("_description");
}
get
{
return _description;
}
}
private double _progress_bar_Value;
public double progress_bar_Value
{
set
{
_progress_bar_Value = value;
RaisePropertyChanged("_progress_bar_Value");
}
get
{
return _progress_bar_Value; //= ProfileSetting.ProfileTab_DOB;
}
}
private double _Total_Bytes;
public double Total_Bytes
{
set
{
_Total_Bytes = value;
RaisePropertyChanged("_Total_Bytes");
}
get
{
return _Total_Bytes;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
`
>
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,0" Orientation="Vertical"
>
<TextBlock Text="{Binding description}"
FontSize="18"
TextWrapping="Wrap"
Foreground="White" x:Name="Totalsize"
/>
<ProgressBar x:Name="Download_progressBar"
IsIndeterminate="False"
Maximum="100"
Height="10"
Width="400"
Value="{Binding progress_bar_Value}"
Foreground="White"
/>
<TextBlock Text="{Binding Bytes_received}"
FontSize="18"
TextWrapping="Wrap"
Foreground="White"
x:Name="Total_received"
/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>`
Raise Property Changed on the public property not backing field (as commented by #HighCore)

can't get a label to update from wpf in another class

I'm making a basic program where a label updates when the user types in a text box. i'm trying to use data binding and INotifyPropertyChanged to work this out, so i don't want any workarounds. i used 2 buttons so i can actually see if they updated. here's my main class
namespace TestStringChangeFromAnotherClass
public partial class MainWindow : Window
{
textClass someTextClass = new textClass();
public MainWindow()
{
InitializeComponent();
}
public string someString1;
public string someString2;
private void btn1_Click(object sender, RoutedEventArgs e)
{
someTextClass.Text1 = tbx1.Text;
}
private void btn2_Click(object sender, RoutedEventArgs e)
{
someTextClass.Text2 = tbx1.Text;
}
}
here's the wpf for it
<Window x:Class="TestStringChangeFromAnotherClass.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="btn1" Content="Button" HorizontalAlignment="Left" Height="36" Margin="29,246,0,0" VerticalAlignment="Top" Width="108" Click="btn1_Click"/>
<Button x:Name="btn2" Content="Button" HorizontalAlignment="Left" Height="36" Margin="227,246,0,0" VerticalAlignment="Top" Width="124" Click="btn2_Click"/>
<Label x:Name="lbl1" Content="{Binding textClass.Text1}" HorizontalAlignment="Left" Height="37" Margin="74,32,0,0" VerticalAlignment="Top" Width="153"/>
<Label x:Name="lbl2" Content="{Binding textClass.Text2, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="38" Margin="74,90,0,0" VerticalAlignment="Top" Width="153"/>
<TextBox x:Name="tbx1" HorizontalAlignment="Left" Height="37" Margin="290,32,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="190"/>
</Grid>
as you can see, i've tried using UpdateSourceTrigger. i've also tried to use "someTestClass.Text1" instead of textClass.Test1, because that's how i defined it in the MainWindow. Here's my textClass
namespace TestStringChangeFromAnotherClass
public class textClass:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string text1;
public string Text1
{
get { return text1; }
set
{
text1 = value;
NotifyPropertyChanged("Text1");
}
}
private string text2;
public string Text2
{
get { return text2; }
set
{
text2 = value;
NotifyPropertyChanged("Text2");
}
}
protected void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
i can't figure out how to get wpf to look for the Test1 or Test2 strings in the separate class and update them when the strings change. i have a feeling the problem lies within DataContext, but i can't figure it out. i'd also rather not use DataContext within c#, only in WPF
UPDATE:
when i debug this, when it gets to NotifyPropertyChanged, PropertyChanged is evaluated as null. could that be the problem?
You bind DataContext to your Window which, as far as I can see, doesn't have textClass property. It has someTextClass field of textClass type. In order for your code to work your can change someTextClass to public property:
public textClass someTextClass { get; private set; }
initialize it in constructor:
public MainWindow()
{
someTextClass = new textClass();
InitializeComponent();
}
and then change binding to point to someTextClass property
<Label x:Name="lbl1" Content="{Binding someTextClass.Text1}" .../>
<Label x:Name="lbl2" Content="{Binding someTextClass.Text2}" .../>
You are binding to the MainWindow class itself as your DataContext, and trying to access the property called someTextClass that has the properties you want to bind to.
You are running into two problems:
1) Your XAML is trying to reference the desired object by it's type, not it's name. Not going to work. Your binding expressions should look like {Binding someTextClass.Text1} (note the difference in the first part of the path expression).
2) You can only bind to public things. Your field is not defined as public, and therefore is private. Even though the XAML should logically "be able to see" the property, as it's the same class, DataBinding will only work on public properties.
3) EDIT: You must also make this a property. WPF will not bind to fields.
In general, using Snoop will help diagnose silent binding errors.

How call a general method with data binding?

I would like to understand how to correctly use MVVM and data binding when we are working with many properties.
<Window x:Class="WpfApplication1.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">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="textBox1" VerticalAlignment="Top" Width="463" Text="{Binding OriginalText, UpdateSourceTrigger=PropertyChanged}" />
<Label Height="28" HorizontalAlignment="Left" Margin="12,242,0,0" Name="label1" VerticalAlignment="Top" Width="463" Content="{Binding ModifiedText}"/>
<CheckBox Content="Upper" Height="16" HorizontalAlignment="Left" Margin="12,41,0,0" Name="checkBox1" VerticalAlignment="Top" />
<CheckBox Content="Underline" Height="16" HorizontalAlignment="Left" Margin="12,63,0,0" Name="checkBox2" VerticalAlignment="Top" />
<CheckBox Content="Bold" Height="16" HorizontalAlignment="Left" Margin="12,85,0,0" Name="checkBox3" VerticalAlignment="Top" />
<CheckBox Content="Shadow" Height="16" HorizontalAlignment="Left" Margin="12,107,0,0" Name="checkBox4" VerticalAlignment="Top" />
<CheckBox Content="Red" Height="16" HorizontalAlignment="Left" Margin="12,129,0,0" Name="checkBox5" VerticalAlignment="Top" />
<CheckBox Content="Scary" Height="16" HorizontalAlignment="Left" Margin="12,151,0,0" Name="checkBox6" VerticalAlignment="Top" />
<CheckBox Content="Remove first letter" Height="16" HorizontalAlignment="Left" Margin="12,173,0,0" Name="checkBox7" VerticalAlignment="Top" />
<CheckBox Content="Remove last letter" Height="16" HorizontalAlignment="Left" Margin="12,195,0,0" Name="checkBox8" VerticalAlignment="Top" />
</Grid>
I have a OriginalText TextBox and a ModifiedText Label. When I check a box I would like to directly apply the modification without having to click a button. How should I do that?
In my ViewModel I created all the properties that are binded to the XAML CheckBox.
private string _originalText = string.Empty;
public string OriginalText
{
get { return _originalText; }
set
{
_originalText = value;
NotifyPropertyChanged("OriginalText");
}
}
private string _modifiedText;
public string ModifiedText
{
get { return _originalText; }
set
{
_originalText = value;
NotifyPropertyChanged("ModifiedText");
}
}
private bool upper;
public bool Upper
{
get { return upper; }
set
{
upper = value;
NotifyPropertyChanged("Upper");
// Should I notify something else here or call a refresh method?
}
}
private bool removeFirstLetter;
public bool RemoveFirstLetter
{
get { return removeFirstLetter; }
set
{
removeFirstLetter = value;
NotifyPropertyChanged("RemoveFirstLetter");
// Should I notify something else here or call a refresh method?
}
}
// ...
Then I created a Work method in the same ViewModel class at this moment. I ll move this method into the business later.
private void Work()
{
string result = _originalText;
if (Upper)
result = result.ToUpper();
if (removeFirstLetter)
result = result.Substring(1, result.Length);
// if ...
ModifiedText = result;
}
My question is when, where should I call the work method? Should I call it in each setter or getter? I dont like the idea. I do something wrong...
Thank you.
In your particular case, you should create a Boolean property using the INotifyPropertyChanged interface. Now bind this property to your "IsChecked" check box property. By calling your Work() method inside the setter, every time the check box is "ticked" the setter will trigger each time.
The answer to your question is very simple: Use Commands.
Commands are MVVM's way to realize the binding to a method in your ViewModel. The implementation of Commands follows a very standard pattern. You will find plenty of information over the Internet here is just a short sketch:
Commands implemented in your ViewModel have to be of type ICommand and every Command has to come along with to methods in your code one responsible for executing the actual method and the other one for checking if the execution is currently possible.
These methods have to be named CanExecute and Execute respectively. It is commonly the case to facilitate the use of several Commands with a small helping class called DelegateCommand which provides delegates for the previously mentioned methods.
Take this class as it is without any modifications:
public class DelegateCommand<T> : ICommand {
private Predicate<T> canExecute;
private Action<T> execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand (Predicate<T> canExecute, Action<T> execute) {
this.canExecute = canExecute;
this.execute = execute;
}
public bool CanExecute (object param) {
return canExecute((T)param);
}
public void Execute (object param) {
execute((T)param);
}
public void CanExecuteChangedRaised () {
CanExecuteChanged(this, new EventArgs());
}
}
Then your Command declarations are of type DelegateCommand rather than of type ICommand. See the following example to illustrate and you will get the idea:
Supose you have a method foo() in your ViewModel you want to be called with a click to a button:
class ViewModel {
// ...
public DelegateCommand<object> FooCommand { get; set; }
public ViewModel () {
FooCommand = new DelegateCommand<object>(CanExecuteFooCommand, ExecuteFooCommand);
}
public bool CanExecuteFooCommand (object param) {
return true;
}
public void ExecuteFooCommand (object param) {
foo();
}
// ...
}
Supposing you have set your ViewModel as the controls DataContext via it's DataContext property the only thing left to do is to bind the FooCommand to your button like this:
That's it!
APPENDIX (referring to comment):
In order to have some action take place without actually hitting the Button you would simply have to track any changed in the UI with your ViewModel and react accordingly - that's what MVVM is about: Track the data from the UI modify or process them and populate them back to the UI.
To react on a TextBox Text change create a corresponding string property in your ViewModel and track whether the new ioncoming value from the View is different to the current textBox text:
private string _text;
public string Text {
get { return _text; }
set {
// the text in the TextBox is about to change.
if (!_text.Equals(value))
{
doSomething();
}
_text = value;
FirePropertyChanged("Text");
}
}
For doing the same with your CheckBox you can apply ICommand as described above since CheckBox is derived from Button and is therefor offering the Command property.

Categories

Resources