Updating WPF labels from static object - c#

I have been trying to get this working all day. I am attempting to learn C# and writing a blackjack program. I have never worked with WPF before and everything about it is extremely confusing and frustrating.
I have three labels on my window that I am trying to update from a static instance of a viewmodel class (BlackjackViewModel):
<Label x:Name="lblPlayerTotal" Content="{Binding Source={x:Static classes:BlackjackViewModel.Instance}, Path=PlayerTotal, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="10,264,0,0" VerticalAlignment="Top" Width="38"/>
<Label x:Name="lblDealerTotal" Content="{Binding Source={x:Static classes:BlackjackViewModel.Instance}, Path=DealerTotal, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="10,160,0,0" VerticalAlignment="Top"/>
<Label x:Name="lblCardsRemaining" Content="{Binding Source={x:Static classes:BlackjackViewModel.Instance}, Path=CardsRemaining, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="584,10,0,0" VerticalAlignment="Top"/>
I can't get anything to appear, the labels are just blank. According to the debugger BlackjackViewModel is sending PropertyChanged events but as far as I can tell nothing is listening. The viewmodel's properties all look like this:
public int DealerTotal {
get { return dealerTotal; }
set
{
dealerTotal = value;
OnPropertyChanged("DealerTotal");
}
}
and a breakpoint at OnPropertyChanged will trigger when the viewmodel is updated so it's sending events.
I tried refactoring my code to make the BlackjackViewModel instance not static and that didn't help either. This was difficult because I have an EventListener set up that updates the viewmodel's count of remaining cards when a card is dealt from the deck so I had to comment it out. It still didn't work with the following XAML:
<Label x:Name="lblPlayerTotal" DataContext="blackjackViewModel" Content="{Binding PlayerTotal}" HorizontalAlignment="Left" Margin="10,264,0,0" VerticalAlignment="Top" Width="38"/>
<Label x:Name="lblDealerTotal" DataContext="blackjackViewModel" Content="{Binding DealerTotal}" HorizontalAlignment="Left" Margin="10,160,0,0" VerticalAlignment="Top"/>
<Label x:Name="lblCardsRemaining" DataContext="blackjackViewModel" Content="{Binding CardsRemaining}" HorizontalAlignment="Left" Margin="584,10,0,0" VerticalAlignment="Top"/>
How do I actually have the labels update when the viewmodel instance updates?
update: With the corrected static singleton setup, the labels contain the number 0, not blank. Here is the ViewModel. When a property is updated, the debugger breaks on a breakpoint set in OnPropertyChanged. I get no binding errors in the Output view. I'll download a WPF tracer.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace BlackjackGame
{
class BlackjackViewModel : INotifyPropertyChanged
{
private static BlackjackViewModel __instance = null;
private int dealerTotal;
private int playerTotal;
private int cardsRemaining;
public event PropertyChangedEventHandler PropertyChanged;
static BlackjackViewModel()
{
__instance = new BlackjackViewModel();
}
public int CardsRemaining
{
get { return cardsRemaining; }
set
{
cardsRemaining = value;
OnPropertyChanged("CardsRemaining");
}
}
public int PlayerTotal
{
get { return playerTotal; }
set
{
playerTotal = value;
OnPropertyChanged("PlayerTotal");
}
}
public int DealerTotal {
get { return dealerTotal; }
set
{
dealerTotal = value;
OnPropertyChanged("DealerTotal");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public static BlackjackViewModel Instance
{
get { return __instance; }
}
}
}

Related

Unable to Update XAML TextBlock Text Binding

I have a TextBlock in XAML that's bound to a property called EditsWarning:
<TextBlock DockPanel.Dock="Top" Text="{Binding EditsWarning, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{DynamicResource Esri_TextBlockRegular}" HorizontalAlignment="Left" FontSize="14" FontWeight="DemiBold" VerticalAlignment="Center" Margin="10,0,10,5" TextWrapping="WrapWithOverflow"/>
The Definition for the EditsWarning Property is here:
public string EditsWarning
{
get { return editsWarningMessage; }
set
{
SetProperty(ref editsWarningMessage, value, () => this.EditsWarning);
}
}
The EditsWarning Property is set to an instance of a class like this:
editsWarning = new OutstandingEditsTextBlock();
editsWarningMessage = editsWarning.EditsWarningMessage.ToString();
And the OutstandingEditsTextBlock class is here, and implements INotifyPropertyChanged
internal class OutstandingEditsTextBlock : INotifyPropertyChanged
{
private string editsWarning;
public OutstandingEditsTextBlock()
{
if (Project.Current.HasEdits)
{
this.editsWarning = "This session/version has outstanding edits.";
}
else
{
this.editsWarning = string.Empty;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string EditsWarningMessage
{
get { return this.editsWarning; }
set
{
this.editsWarning = value;
this.OnPropertyChanged("EditsWarningMessage");
}
}
public void OnPropertyChanged(string propertyName)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I noticed that I can get it to display either value, however, I can never get it to update in the same debugging session. In fact, it looks like the setter for the public property is never hit.
Can someone please help me figure out what I'm doing wrong?
Thank you.

Data Binding Issue

I am having a major problem binding my data from TextBox to ViewModel To TextBlock. I have set up my following Xaml Code like so:
<Page
x:Class="digiBottle.MainPage"
DataContext="{Binding Source=UserProfile}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:digiBottle"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
DataContext="{Binding Source=UserProfile}">
<TextBlock HorizontalAlignment="Left" Margin="219,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="32" Width="232" Text="{Binding userFirstName, Mode=OneWay}"/>
<TextBox HorizontalAlignment="Left" Margin="39,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="111" Text="{Binding userFirstName, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
The .cs file I am trying to use as a source is defined as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace digiBottle.Model
{
public class UserProfile : INotifyPropertyChanged
{
public int ID { get; set; }
public string userFirstName;
public string userLastName { get; set; }
public int age { get; set; }
public int weight { get; set; }
public int height { get; set; }
public DateTime signupTime { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public UserProfile()
{
userFirstName = "First Name";
}
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public UserProfile getCopy()
{
UserProfile copy = (UserProfile)this.MemberwiseClone();
return copy;
}
}
}
What am i doing wrong when trying to bind my TextBox and TextBlock to userFirstName in the UserProfile.cs Source. ANy help would be a major help!
Thank you
The first thing I notice here is that your properties (setter) are not raising event change. You need to call RaisePropertyChanged in your properties setter.
I would have written it like
A private field
private String _userFirstName;
Then in constructor
public UserProfile()
{
this._userFirstName = "First Name";
}
With Property raising event
public String UserFirstName
{
get { return this._userFirstName; }
set
{
this._userFirstName = value;
this.RaisePropertyChanged("UserFirstName");
}
}
And then in XAML, bind it with property "UserFirstName" with two way binding
<TextBlock HorizontalAlignment="Left" Margin="219,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="32" Width="232" Text="{Binding UserFirstName, Mode=OneWay}"/>
<TextBox HorizontalAlignment="Left" Margin="39,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="111" Text="{Binding UserFirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
DataBinding can be hard to understand at first. Please refer to Data binding for Windows Phone 8 to get yourself started.
For your code: Here are the fixes you will need:
Remember you can only bind to a property.
You need to raise the event on the set action.
You may need twoway binding on the textbox depending on the actions you want.
You need to set the DataContext for both textbox and the textblock.
Here are the changes:
CS
public class UserProfile : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string user_first_name;
public String UserFirstName
{
get { return user_first_name; }
set
{
user_first_name = value;
OnPropertyChanged("UserFirstName");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
UserProfile up = new UserProfile();
this.tb1.DataContext = up;
this.tb2.DataContext = up;
}
}
XAML
<TextBlock x:Name="tb2" TextWrapping="Wrap" Text="{Binding UserFirstName}"/>
<TextBox x:Name="tb1" HorizontalAlignment="Left" Height="72" Margin="14,475,0,0" Grid.Row="1" TextWrapping="Wrap" Text="{Binding UserFirstName, Mode=TwoWay}" VerticalAlignment="Top" Width="456" />

Label that counts when button is pushed doesn't work in WPF following MVVM pattern

I'm a C# newbie and I'm trying to make a simple program that has two buttons and a label. Buttons are "up" and "down", and the number in the label changes according to them. When I execute the program, label shows the initial value which defined in viewmodel. But when I push buttons, it doesn't update the value. I couldn't figure out which is not working, buttons or label when an update occurs. Thanks for your time.
I think the problem is the usage "RaisePropertyChanged". I have no idea how should I use it. If someone explains it, it would be good.
Buttons and Label in view:
<Button x:Name="Up" Content="Up" HorizontalAlignment="Left" Grid.Row="6" Grid.Column="1" VerticalAlignment="Top" Width="75" Grid.ColumnSpan="2" Command="{Binding UpCommand}"/>
<Button x:Name="Down" Content="Down" Grid.Column="3" HorizontalAlignment="Left" Grid.Row="6" VerticalAlignment="Top" Width="75" Command="{Binding DownCommand}" />
<Label x:Name="Number" HorizontalAlignment="Left" Height="31" Grid.Row="4" VerticalAlignment="Top" Width="161" Content="{Binding Path=Lol, UpdateSourceTrigger=PropertyChanged}" Background="#FFB95151" Grid.ColumnSpan="3" Grid.Column="1" />
this is ViewModel:
class MainViewModel : BaseINPC
{
//Number class to hold the number
private Number lol; //I'm not good at naming
public Number Lol
{
get
{
return lol;
}
set
{
lol = value;
RaisePropertyChanged("Lol");
}
}
public MainViewModel()
{
lol = new Number { number = 5 }; //initial value
RaisePropertyChanged("Lol");
}
#region Commands
//Up botton commands
void UpNumber()
{
lol.change(true); //it adds 1 to number when True, substracts 1 when false
RaisePropertyChanged("Lol");
}
bool CanUpNumber()
{
return true;
}
public DelegateCommand UpCommand { get { return new DelegateCommand(UpNumber, CanUpNumber); } }
//Down button
void DownNumber()
{
lol.change(false);
RaisePropertyChanged("Lol");
}
bool CanDownNumber()
{
return true;
}
public DelegateCommand DownCommand { get { return new DelegateCommand(DownNumber, CanDownNumber); } }
#endregion
}
This is in my Model :
class Number : INotifyPropertyChanged
{
private int _number;
public int number
{
get { return _number; }
set
{
_number = value;
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public void change(bool up)
{
if (up)
_number++;
else
_number--;
}
public override String ToString()
{
return "" + _number;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
in your Number class
public void change(bool up)
{
if (up)
_number++;
else
_number--;
RaisePropertyChanged("number");
}
or
public int number
{
get { return _number; }
set
{
_number = value;
RaisePropertyChanged("number");
}
}
public void change(bool up)
{
if (up)
number++;
else
number--;
}
you were missing notification for the number property
also change the content binding of Label to Content="{Binding Path=Lol.number}" since number is the real property which actually change when method change() is executed.
Lol may not necessary to be notified if not changing while runtime, also UpdateSourceTrigger=PropertyChanged is unrelevant with binding to label so perhaps remove that too.

Why TextBlock Text is not updating in ViewModel?

I am trying to post some values to the next screen through XML serialization and Isolated Storage. Values are posted from textbox and also from textBlock to the next screen's textBlocks with button click event. But the TextBlock's text is not posted to the next screen, only the textBox text is posted. If I debug and check, XML shows as "" with no string loadedinto it. It's also not binding as well. Why?
//MainPage.xaml
<StackPanel>
<Button Margin="10"
Content="Simple Sample"
Name="btnSimple"
Click="btnSimple_Click">
</Button>
<TextBlock TextWrapping="Wrap" Text="{Binding FirstName, Mode=TwoWay}"/>
<TextBlock TextWrapping="Wrap" Text="{Binding MyText, Mode=TwoWay}"/>
</StackPanel>
//MainPage.xaml.cs
using System;
using System.Windows;
using Microsoft.Phone.Controls;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text;
using System.Xml.Serialization;
namespace WPIsolatedStorage
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
private LastUser _User = new LastUser();
private const string USER_KEY = "LastUser";
public MainPage()
{
InitializeComponent();
}
private void GetUser()
{
string xml;
xml = IsolatedStorageSettings.ApplicationSettings[USER_KEY].ToString();
using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(xml)))
{
XmlSerializer serializer = new XmlSerializer(typeof(LastUser));
_User = (LastUser)serializer.Deserialize(ms);
}
}
private void btnSimple_Click(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Uri("/PageSimple.xaml", UriKind.Relative));
}
private void PhoneApplicationPage_Loaded_1(object sender, RoutedEventArgs e)
{
if (IsolatedStorageSettings.ApplicationSettings.Contains(USER_KEY))
GetUser();
this.DataContext = _User;
}
}
}
//LastUser.cs
using System.ComponentModel;
namespace WPIsolatedStorage
{
public class LastUser : INotifyPropertyChanged
{
#region INotifyPropertyChanged Event
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Private Variables
private string mFirstName = string.Empty;
private string mTextBlock = string.Empty;
#endregion
#region Public Variables
public string FirstName
{
get { return mFirstName; }
set
{
if (mFirstName != value)
{
mFirstName = value;
RaisePropertyChanged("FirstName");
}
}
}
public string MyText
{
get { return this.mTextBlock; }
set
{
this.mTextBlock = value;
this.RaisePropertyChanged("MyText");
}
}}
}
//SeconPage.xaml
<TextBox Grid.Column="1"
Grid.Row="0"
Text="{Binding FirstName, Mode=TwoWay}"
Name="txtFirstName" />
<TextBlock Grid.Column="1" Name="MyTextBlock" HorizontalAlignment="Left" Margin="12,178,0,-224" Grid.Row="4" TextWrapping="Wrap" Text="{Binding MyText, Mode=TwoWay}" VerticalAlignment="Top" Height="93" Width="191" FontSize="36"/>
There is a typo in you binding expression. Should be
<TextBlock Grid.Column="1" .... Text="{Binding MyText}" ../>
instead of
<TextBlock Grid.Column="1" .... Text="{Binding Mytext}" ../>
My second assumption is: you use a simple binding in your code
<TextBlock TextWrapping="Wrap" Text="{Binding MyText, Mode=TwoWay}"/>
But in Windows Phone the binding works only after you unfocused control. You can find a discussion and few solutions here:
TextBox Binding TwoWay Doesn't Update Until Focus Lost WP7
Try This.
Code snippet[C#]:
public class Data : INotifyPropertyChanged
{
private int customerID;
public int CustomerID
{
get { return customerID; }
set { customerID = value; OnPropertyChanged("CustomerID"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
Code snippet[XAML]:
<TextBlock Content="{Binding CustomerID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

mvvm confusion with canexecute and binding commands

I'm having a seriously hard time wrapping my head around the logic in the tutorials and posts about this subject. I'm trying to implement it in a wpf application I'm writing.
Basically, I'm using a listbox to display a ToString of objects in a list and allowing users to add and remove from that list and its corresponding listbox via an add and remove button. The problem I'm having is with the implementation of the Remove button. I want the button disabled if no listbox item is selected, which is one of the things that this pattern is good for. I'm lost as to how to implement that condition.
At the moment, the button is not enabling when I highlight a listbox item. I suppose the CanExecuteChanged event isn't firing.. How do I need to change this?
my CommandsHandler class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace TechopsTools
{
public class CommandHandler : ICommand
{
private Action<object> _execute;
private bool _canExecute;
public CommandHandler(Action<object> execute)
: this(execute, true)
{
}
public CommandHandler(Action<object> execute, bool canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
my viewmodel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using ProtoBuf;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace TechopsTools
{
class LogCheckClientViewModel : INotifyPropertyChanged
{
private string uri;
private string response;
private bool _canRemove;
private LogConstraints selectedConstraints;
private ObservableCollection<LogConstraints> constraints;
public event PropertyChangedEventHandler PropertyChanged;
public LogConstraints SelectedConstraints
{
get
{
return selectedConstraints;
}
set
{
selectedConstraints = value;
OnPropertyChanged("SelectedConstraints");
}
}
private CommandHandler removeItemCommand;
public ICommand RemoveItemCommand
{
get
{
if (removeItemCommand == null)
removeItemCommand = new CommandHandler(param => RemoveConstraint(), SelectedConstraints != null);
return removeItemCommand;
}
}
public string Response
{
get
{
return response;
}
set
{
response = value;
OnPropertyChanged("Response");
}
}
public string Uri
{
get
{
return uri;
}
set
{
uri = value;
OnPropertyChanged("Uri");
}
}
public ObservableCollection<LogConstraints> Constraints
{
get
{
return constraints;
}
set
{
constraints = value;
OnPropertyChanged("Constraints");
}
}
public LogCheckClientViewModel()
{
constraints = new ObservableCollection<LogConstraints>();
}
public void AddConstraint()
{
NewConstraint newConstraint = new NewConstraint();
newConstraint.ShowDialog();
if (newConstraint._vm.constraint != null)
{
constraints.Add(newConstraint._vm.constraint);
}
}
private void RemoveConstraint()
{
Constraints.Remove(SelectedConstraints);
OnPropertyChanged("Constraints");
}
xaml:
<Window x:Class="TechopsTools.LogCheckClient"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TechopsTools"
Title="LogCheck" Height="453.057" Width="495.986">
<Grid>
<TextBox Text="{Binding Response}" HorizontalAlignment="Left" Height="128" Margin="38,212,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="413" VerticalScrollBarVisibility="Auto" IsEnabled="False"/>
<Label Content="Response" HorizontalAlignment="Left" Margin="38,188,0,0" VerticalAlignment="Top" Width="78" Height="24"/>
<TextBox Text="{Binding Uri}" HorizontalAlignment="Left" Height="23" Margin="38,26,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="413"/>
<Label Content="Uri" HorizontalAlignment="Left" Margin="38,0,0,0" VerticalAlignment="Top" Width="78" Height="24"/>
<Button Content="Add Constraint" HorizontalAlignment="Left" Margin="38,54,0,0" VerticalAlignment="Top" Width="127" Height="56" Click="Add_Click"/>
<Button x:Name="Submit" Content="Submit Request" HorizontalAlignment="Left" Margin="38,345,0,0" VerticalAlignment="Top" Width="413" Height="70" Click="Submit_Click"/>
<ListBox SelectedItem="{Binding Path=SelectedConstraints,UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding Constraints}" HorizontalAlignment="Left" Height="124" Margin="182,54,0,0" VerticalAlignment="Top" Width="269">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=description}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding RemoveItemCommand}" Content="Remove Constraint" HorizontalAlignment="Left" Margin="38,122,0,0" VerticalAlignment="Top" Width="127" Height="56" />
</Grid>
</Window>
You really need to be using a CanExecute delegate the same way you're doing an Execute handler.
Basically right now you are checking if it can execute when RemoveItemCommand is first accessed. But then it just keeps that value the entire time.
If you pass in a delegate with that same condition (perhaps adding in for an empty list, not just a null list), I'm betting it'll work.
In other words, in your CommandHandler, change
private bool _canExecute;
to
private Func<bool,object> _canExecute;
and change
public bool CanExecute(object parameter)
{
return _canExecute;
}
to
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
and then in your ViewModel, change
removeItemCommand = new CommandHandler(param => RemoveConstraint(),
SelectedConstraints != null);
to
removeItemcommand = new CommandHandler(param => RemoveConstraint(),
param => SelectedConstraints != null);
(note that this might not be exactly the right code, as I am just writing it freehand, but hopefully you get the point)
I think that thing can be done inside your XAML file, just using DataTrigger.
If this solution would satisfied you, please let me know writing comment, and I will provide you some code.
Best regards

Categories