I have an object class that derives from BoxView. The object has TouchEffect attached and when the OnTouchAction starts, I change the object's properties.
I would also like to change the property of a label based on the text attached to the string that label is bound to.
What I did is I created an instance of the page that contains bindable string and label, then, I tried to change the value of string by referencing it in code inside the OnTouchAction method.
I don't get errors and the Breakpoint tells me that code arrives to the line, but the label is not being updated.
I am trying to update the string from the class that is not the same where the string is.
Is there anyone who could help me out here?
class Element : BoxView
{
List<long> ids = new List<long>();
MainPage mainPage = new MainPage();
public event EventHandler StatusChanged;
public Element()
{
TouchEffect effect = new TouchEffect();
effect.TouchAction += OnTouchEffectAction;
Effects.Add(effect);
}
public Color DefaultColor { set; get; }
public Color HighlightColor { set; get; }
public bool IsPressed { private set; get; }
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
switch (args.Type)
{
case TouchActionType.Pressed:
AddToList(args.Id);
mainPage.LeftLabelText = "entered";
break;
case TouchActionType.Entered:
if (args.IsInContact)
{
AddToList(args.Id);
mainPage.LeftLabelText = "entered";
}
break;
case TouchActionType.Moved:
break;
case TouchActionType.Released:
case TouchActionType.Exited:
RemoveFromList(args.Id);
break;
}
}
void AddToList(long id)
{
if (!ids.Contains(id))
{
ids.Add(id);
}
CheckList();
}
void RemoveFromList(long id)
{
if (ids.Contains(id))
{
ids.Remove(id);
}
CheckList();
}
void CheckList()
{
if (IsPressed != ids.Count > 0)
{
IsPressed = ids.Count > 0;
Color = IsPressed ? HighlightColor : DefaultColor;
mainPage.LeftLabelText = "entered";
StatusChanged?.Invoke(this, EventArgs.Empty);
}
}
}
and the MainPage relevant code:
Element element = new Element();
element.HighlightColor = Color.Accent;
element.DefaultColor = Color.Transparent;
element.Color = bar.DefaultColor;
element.HeightRequest = sGrid.Height;
element.VerticalOptions = LayoutOptions.End;
(...) // adding element view to the grid.
The string:
public string _leftLabelText = "testing";
public string LeftLabelText
{
get => _leftLabelText;
set
{
_leftLabelText = value;
NotifyPropertyChanged("LeftLabelText");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged2;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged2?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
<Label x:Name="leftLabel" x:FieldModifier="public" Text="{Binding LeftLabelText, Mode=TwoWay}" TextColor="Black" FontSize="10" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"/>
Related
Here is code I have been working on with the help of Deczalof:
Here is the XAML code that I have
<t:PopupEntryFrame2 x:Name="newDeckNameEntry" TextChanged="newDeckNameEntry_TextChanged" />
With code behind:
public partial class CopyDeckPopup : Rg.Plugins.Popup.Pages.PopupPage
{
string originalName;
string originalDescription;
List<Deck> listDecks;
public CopyDeckPopup(string clickedDeckName, string clickedDeckDescription)
{
InitializeComponent();
listDecks = App.DB.GetAllDecks();
newDeckNameEntry.Text = clickedDeckName;
newDeckDescriptionEntry.Text = clickedDeckDescription;
originalName = clickedDeckName;
originalDescription = clickedDeckDescription;
OK_Button.IsEnabled = false;
}
private async void Cancel_Button_Clicked(object sender, EventArgs e)
{
await PopupNavigation.Instance.PopAsync(false);
}
private async void OK_Button_Clicked(object sender, EventArgs e)
{
if (IsBusy)
return;
IsBusy = true;
await PopupNavigation.Instance.PopAsync(false);
var newDeckNameEntryTextTrim = newDeckNameEntry.Text.Trim();
var newDeckDescriptionEntryTextTrim = newDeckDescriptionEntry.Text.Trim();
if (newDeckNameEntryTextTrim != originalName || newDeckDescriptionEntryTextTrim != originalDescription)
{
App.DB.CopyDeckToDb2(originalName, newDeckNameEntryTextTrim, newDeckDescriptionEntryTextTrim);
MessagingCenter.Send<PopupPage>(new PopupPage(), "PageRefresh");
}
IsBusy = false;
}
void newDeckNameEntry_TextChanged(object sender, EntryTextChangedEventArgs e)
{
NewDeckNameEntryValidator(e.NewTextValue);
}
void newDeckDescriptionEntry_TextChanged(object sender, EntryTextChangedEventArgs e)
{
var deckName = newDeckNameEntry.Text.Trim();
var isDeckAvailable = listDecks.Where(x => x.Name == deckName).SingleOrDefault();
if (isDeckAvailable == null)
{
OK_Button.IsEnabled = e.NewTextValue != originalDescription ? true : false;
}
}
void NewDeckNameEntryValidator(string newDeckNameEntry)
{
var newDeckNameEntryTrimmed = newDeckNameEntry.Trim();
var isDeckNameAvailable = listDecks.Where(x => x.Name == newDeckNameEntryTrimmed).SingleOrDefault();
if (string.IsNullOrWhiteSpace(newDeckNameEntryTrimmed) ||
isDeckNameAvailable != null ||
newDeckNameEntryTrimmed.StartsWith("::") ||
newDeckNameEntryTrimmed.EndsWith("::") ||
newDeckNameEntryTrimmed.Count(c => c == ':') > 2)
{
OK_Button.IsEnabled = false;
return;
}
OK_Button.IsEnabled = true;
}
}
and the C# code for a template:
public class PopupEntryFrame2 : CustomFrame
{
CustomEntry entry { get; set; }
public PopupEntryFrame2()
{
entry = new CustomEntry();
entry.SetBinding(PopupEntryFrame2.TextProperty, new Binding("Text", source: this));
entry.TextChanged += (s, a) =>
{
OnTextChanged(new EntryTextChangedEventArgs(a.NewTextValue, a.OldTextValue));
};
Content = entry;
CornerRadius = 5;
HasShadow = false;
SetDynamicResource(BackgroundColorProperty, "EntryFrameBackgroundColor");
SetDynamicResource(BorderColorProperty, "EntryFrameBorderColor");
SetDynamicResource(CornerRadiusProperty, "EntryFrameCornerRadius");
SetDynamicResource(HeightRequestProperty, "PopupEntryFrameHeight");
SetDynamicResource(MarginProperty, "PopupEntryFrameMargin");
SetDynamicResource(PaddingProperty, "PopupEntryFramePadding");
}
public class EntryTextChangedEventArgs : EventArgs
{
public EntryTextChangedEventArgs(String newValue = null, String oldValue = null)
{
NewTextValue = newValue;
OldTextValue = oldValue;
}
public String NewTextValue { get; }
public String OldTextValue { get; }
}
public event EventHandler TextChanged;
protected virtual void OnTextChanged(EntryTextChangedEventArgs args)
{
TextChanged?.Invoke(this, args);
}
public static readonly BindableProperty TextProperty =
BindableProperty.Create(nameof(Text), typeof(string), typeof(PopupEntryFrame2), default(string));
public string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
}
The error I get when building is this:
CopyDeckPopup.xaml(22,63): XamlC error XFC0002: EventHandler "newDeckNameEntry_TextChanged"
with correct signature not found in type "DecksTab.Pages.DeckOptions.CopyDeckPopup"
To achieve your goal you can simply add the Entry on your PopupEntryFrame class and define an Event there that connects with the TextChanged event in the original Entry.
This is done as illustrated in the code below (which is based on yours!)
using Test.Renderers;
namespace Test.Templates
{
public class PopupEntryFrame : CustomFrame
{
Entry entry { get; set; }
public PopupEntryFrame()
{
entry = new Entry();
entry.TextChanged += (s, a) =>
{
OnTextChanged(new EntryTextChangedEventArgs());
};
Content = entry;
CornerRadius = 5;
HasShadow = false;
SetDynamicResource(BackgroundColorProperty, "EntryFrameBackgroundColor");
SetDynamicResource(BorderColorProperty, "EntryFrameBorderColor");
SetDynamicResource(CornerRadiusProperty, "EntryFrameCornerRadius");
SetDynamicResource(HeightRequestProperty, "PopupEntryFrameHeight");
SetDynamicResource(MarginProperty, "PopupEntryFrameMargin");
SetDynamicResource(PaddingProperty, "PopupEntryFramePadding");
}
public class EntryTextChangedEventArgs : EventArgs
{
// class members
}
public event EventHandler TextChanged;
protected virtual void OnTextChanged(EntryTextChangedEventArgs args)
{
TextChanged?.Invoke(this, args);
}
}
}
And that's it. By doing that you can now write code like
<t:PopupEntry x:Name="newDeckDescriptionEntry" TextChanged="newDeckDescriptionEntry_TextChanged">
Update
In the comments someone suggested using ContentView, so let's take a look at how the same result could be achieved using that approach.
Disclaimer
First of all, it is important to know that Frame inherits itself from ContentView (from which acctualy it inherits its Content property!). In fact, from the documentation we know that
[Xamarin.Forms.ContentProperty("Content")]
[Xamarin.Forms.RenderWith(typeof(Xamarin.Forms.Platform._FrameRenderer))]
public class Frame : Xamarin.Forms.ContentView, Xamarin.Forms.IBorderElement, Xamarin.Forms.IElementConfiguration<Xamarin.Forms.Frame>
which means that by creating a Class/Control that inherits from Frame means that we are already using the ContentView approach.
Create the ContentView
First of all we create a ContentView and set its content to a new PopupFrame() which itself contains an Entry, as follows
public class PopupEntry : ContentView
{
Entry entry { get; set; }
public PopupEntry()
{
entry = new Entry();
Content = new PopupFrame()
{
Content = entry
};
}
}
Add an Event
Next, as is required by the OP, we define an Event for our ContentView that will be triggered when the Text in the Entry changed. Following the Documentation, this can be achieved by adding the following piece of code:
public class EntryTextChangedEventArgs : EventArgs
{
// class members
}
public event EventHandler TextChanged;
protected virtual void OnTextChanged(EntryTextChangedEventArgs args)
{
TextChanged?.Invoke(this, args);
}
Now, we can "link" the original TextChanged event from the Entry control to the new Event of our ContentView, as follows:
entry.TextChanged += (s, a) =>
{
OnTextChanged(new EntryTextChangedEventArgs());
};
Then, our ContentView code will look like
public class PopupEntry : ContentView
{
Entry entry { get; set; }
public PopupEntry()
{
entry = new Entry();
entry.TextChanged += (s, a) =>
{
OnTextChanged(new EntryTextChangedEventArgs());
};
Content = new PopupFrame()
{
Content = entry
};
}
public class EntryTextChangedEventArgs : EventArgs
{
// class members
}
public event EventHandler TextChanged;
protected virtual void OnTextChanged(EntryTextChangedEventArgs args)
{
TextChanged?.Invoke(this, args);
}
}
Wrapping up
With this ContentView defined, we can now write code like
<t:PopupEntry x:Name="newDeckDescriptionEntry" TextChanged="newDeckDescriptionEntry_TextChanged"/>
And that's it! I hope this was useful.
Happy coding!
P.S.:
A little note about the Event declaration: Since EntryTextChangedEventArgs is a copy of the original TextChangedEventArgs we can define the EntryTextChangedEventArgs class like
public class EntryTextChangedEventArgs : EventArgs
{
public EntryTextChangedEventArgs(String newValue = null, String oldValue = null)
{
NewTextValue = newValue;
OldTextValue = oldValue;
}
public String NewTextValue { get; }
public String OldTextValue { get; }
}
and then when instantiating this class we just feed it directly with the values from TextChangedEventArgs, as follows
entry = new Entry();
entry.TextChanged += (s, a) =>
{
OnTextChanged(new EntryTextChangedEventArgs(a.NewTextValue, a.OldTextValue));
};
I have a class for animation behavior like:
public class BaseAnimationBehavior : Behavior<View>
{
protected View associatedObject;
protected Easing _easingFunction;
private readonly BindableProperty EasingFunctionProperty = BindableProperty.Create(nameof(EasingFunction), typeof(string), typeof(BaseAnimationBehavior), defaultValue: "Linear", propertyChanged: (b, o, n) => OnEasingFunctionChanged(b, (string)o, (string)n));
private readonly BindableProperty ScaleProperty = BindableProperty.Create(nameof(Scale), typeof(double), typeof(BaseAnimationBehavior), defaultValue: 1.25);
public string EasingFunction
{
get { return (string)GetValue(EasingFunctionProperty); }
set { SetValue(EasingFunctionProperty, value); }
}
public double Scale
{
get { return (double)GetValue(ScaleProperty); }
set { SetValue(ScaleProperty, value); }
}
protected override void OnAttachedTo(View bindable)
{
associatedObject = bindable;
associatedObject.SizeChanged += AnimateItem;
}
protected override void OnDetachingFrom(View bindable)
{
associatedObject.SizeChanged -= AnimateItem;
}
private static Easing GetEasing(string easingName)
{
switch (easingName)
{
case "BounceIn": return Easing.BounceIn;
case "BounceOut": return Easing.BounceOut;
case "CubicInOut": return Easing.CubicInOut;
case "CubicOut": return Easing.CubicOut;
case "Linear": return Easing.Linear;
case "SinIn": return Easing.SinIn;
case "SinInOut": return Easing.SinInOut;
case "SinOut": return Easing.SinOut;
case "SpringIn": return Easing.SpringIn;
case "SpringOut": return Easing.SpringOut;
default: throw new ArgumentException(easingName + " is not valid");
}
}
private static void OnEasingFunctionChanged(BindableObject bindable, string oldvalue, string newvalue)
{
var obj = bindable as BaseAnimationBehavior;
if (obj == null)
{
return;
}
obj.EasingFunction = newvalue;
obj._easingFunction = GetEasing(newvalue);
}
private void AnimateItem(object sender, EventArgs e)
{
await associatedObject.TranslateTo(associatedObject.TranslationX, associatedObject.TranslationY - 10, 10, _easingFunction);
await associatedObject.TranslateTo(associatedObject.TranslationX, associatedObject.TranslationY + 10, 900, _easingFunction);
}
}
in XAML page, I apply animation like:
<StackLayout IsVisible="{Binding AProp}">
<Entry Text="{Binding AField}" IsEnabled="True">
<Entry.Behaviors>
<Entry.Behaviors:BaseAnimationBehavior EasingFunction="SinInOut" />
</Entry.Behaviors>
</Entry>
</StackLayout>
When AProp becomes true then animation occur because of SizeChanged event.
When AProp becomes false then animation won't run or won't be visible because entire StackLayout is hidden.
I use MVVM and I have a view model and it is binded to view like:
public class MyViewModel
{
public bool AProp { get { return _prop; } set { _prop = value; RaisePropertyChanged ....; } }
public string AField { get { return _field; } set { _field = value; RaisePropertyChanged ....; } }
public void DoSomething() {
if(condition) { AProp = true; } else { AProp = false; }
}
}
How to achieve the situation when hide element, to animate first and then hide it ?
If you are hiding using the View Model you would need to send some kind of signal to the view that the property has changed.
If you have to hide the control from the View Model for some reason I would suggest
overriding the RaisePropertyChanged in your view model
rise an event on the property name is AProb
subscribe to that event in the view
finally start your animation manually by giving the Entry a name
Would anyone know if its possible to bind data to a SfGauge?
If so how according to the needle pointer value,
NeedlePointer needlePointer = new NeedlePointer();
needlePointer.Value = 60;
needlePointer.Color = Color.Gray;
needlePointer.KnobColor = Color.FromHex("#2bbfb8");
needlePointer.Thickness = 5;
needlePointer.KnobRadius = 20;
needlePointer.LengthFactor = 0.8;
scale.Pointers.Add(needlePointer);
Thanks
Here is my Updated code however still showing zero on the guage.
namespace Drip
{
public partial class Guage : ContentPage
{
private const string Url = "https://thingspeak.com/channels/301726/field/1.json";
private HttpClient _client = new HttpClient();
private ObservableCollection<Feed> _data2;
SfCircularGauge circular;
NeedlePointer needlePointer;
public Guage()
{
...
needlePointer = new NeedlePointer();
needlePointer.Color = Color.Gray;
needlePointer.KnobColor = Color.FromHex("#2bbfb8");
needlePointer.Thickness = 5;
needlePointer.KnobRadius = 20;
needlePointer.LengthFactor = 0.8;
scale.Pointers.Add(needlePointer);
Content = circular;
}
protected override async void OnAppearing()
{
var content = await _client.GetStringAsync(Url);
var data = JsonConvert.DeserializeObject<RootObject>(content);
_data2 = new ObservableCollection<Feed>(data.Feeds);
this.BindingContext = _data2[0];
needlePointer.SetBinding(Pointer.ValueProperty, "Field1");
}
}
Model
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Drip
{
public class Feed : INotifyPropertyChanged
{
private decimal _field1 = 0;
public decimal Field1
{
get
{
return _field1;
}
set
{
if (_field1 != value)
{
_field1 = value;
OnPropertyChanged();
}
}
}
public DateTime Created_at { get; set; }
public int Entry_id { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyField1 = null)
{
{
var handler = PropertyChanged;
if (handler != null)
{
PropertyChangedEventArgs propertyChangedEvent
= new PropertyChangedEventArgs(propertyField1);
handler(this, propertyChangedEvent);
}
}
}
}
}
I have changed the following above to my code however the guage is still showing value zero, have i missed anything or used something wrong?
assuming you want to bind to the property "Value" of your BindingContext
needlePointer.SetBinding(NeedlePointer.ValueProperty, "Value");
in order for data binding to work, you need to set a BindingContext
this.BindingContext = _data2[0];
The value which we are getting for _data2[0] from json is 0. Please refer the following attached screenshot of the json data.
In the attached screenshot, highlighted the field1 item value of 0th index from the collection. So, if you bound those value with the needle pointer it will show only the 0 value. Can you please ensure what value you are getting from _data2[0]? If you are getting 0 then please get the field1 value from _data[1] or _data[2].
there is probably a really simple reason why this isnt working but I've tried everything. I have a TextBlock with Text bound to a variable, the variable changes but the Text doesn't :
<TextBlock x:Name="modeLabel" Style="{StaticResource IndiTextBlock}" Height="23" TextWrapping="Wrap" Grid.Row="0" Text="{Binding ModeLabelText}" Margin="35,22,58,0"/>
The code that controls the text value is in a viewmodel:
public string ModeLabelText { get { return _modeLabeltext; } }
public ComboBoxItem SelectedMode { get { return _selectedMode; }
set
{
if (_selectedMode == value) return;
_selectedMode = value;
ToggleMode(null);
EvaluateScenario(null);
}
and
private void ToggleMode(object parameter)
{
if (_isBasicCalculation)
{
_modeLabeltext = "Target profit";
_isBasicCalculation = false;
}
else
{
_modeLabeltext = "Total to invest";
_isBasicCalculation = true;
}
}
Your class has to implement the INotifyPropertyChanged interface, and on changes of your variables, you should trigger the event
public class Model : INotifyPropertyChanged
{
public event EventHandler PropertyChanged; // event from INotifyPropertyChanged
protected void RaisePropertyChanged(string propertyName)
{
var local = PropertyChanged;
if (local != null)
{
local.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public void ToggleMode()
{
// ... your code ...
RaisePropertyChanged("ModelLabelText");
}
}
Thank you Nguyen Kien
private void ToggleMode(object parameter)
{
if (_isBasicCalculation)
{
_modeLabeltext = "Target profit";
OnPropertyChanged("ModeLabelText");
_isBasicCalculation = false;
}
else
{
_modeLabeltext = "Total to invest";
OnPropertyChanged("ModeLabelText");
_isBasicCalculation = true;
}
}
If I had an Observable collection like so :
public ObservableCollection<SpecialPriceRow> SpecialPriceRows = new ObservableCollection<SpecialPriceRow>();
SpecialPriceRow class :
public class SpecialPriceRow : INotifyPropertyChanged
{
public enum ChangeStatus
{
Original,
Added,
ToDelete,
Edited
}
public ChangeStatus Status { get; set; }
public string PartNo { get; set; }
private decimal _price;
public decimal Price
{
get
{
return _price;
}
set
{
if (value != _price)
{
_price = value;
Status = ChangeStatus.Edited;
OnPropertyChanged("Status");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Would it be possible for me to bind a Label in the XAML to the count of objects that are say ... Added? So I could get something like this :
Where green is the count of "Added" objects within the collection. How would I go about doing something like this?
I've written up a ViewModel which will perform the desired functionality you are looking for.
class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<SpecialPriceRow> _SpecialPriceRows = new ObservableCollection<SpecialPriceRow>();
private int _Original = 0;
private int _Added = 0;
private int _ToDelete = 0;
private int _Edited = 0;
public VM()
{
PropertyChanged = new PropertyChangedEventHandler(VM_PropertyChanged);
//The following lines in the constructor just initialize the SpecialPriceRows.
//The important thing to take away from this is setting the PropertyChangedEventHandler to point to the UpdateStatuses() function.
for (int i = 0; i < 12; i++)
{
SpecialPriceRow s = new SpecialPriceRow();
s.PropertyChanged += new PropertyChangedEventHandler(SpecialPriceRow_PropertyChanged);
SpecialPriceRows.Add(s);
}
for (int j = 0; j < 12; j+=2)
SpecialPriceRows[j].Price = 20;
}
private void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
private void SpecialPriceRow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Status")
UpdateStatuses();
}
public ObservableCollection<SpecialPriceRow> SpecialPriceRows
{
get
{
return _SpecialPriceRows;
}
}
private void UpdateStatuses()
{
int original = 0, added = 0, todelete = 0, edited = 0;
foreach (SpecialPriceRow SPR in SpecialPriceRows)
{
switch (SPR.Status)
{
case SpecialPriceRow.ChangeStatus.Original:
original++;
break;
case SpecialPriceRow.ChangeStatus.Added:
added++;
break;
case SpecialPriceRow.ChangeStatus.ToDelete:
todelete++;
break;
case SpecialPriceRow.ChangeStatus.Edited:
edited++;
break;
default:
break;
}
}
Original = original;
Added = added;
ToDelete = todelete;
Edited = edited;
}
public int Original
{
get
{
return _Original;
}
set
{
_Original = value;
PropertyChanged(this, new PropertyChangedEventArgs("Original"));
}
}
public int Added
{
get
{
return _Added;
}
set
{
_Added = value;
PropertyChanged(this, new PropertyChangedEventArgs("Added"));
}
}
public int ToDelete
{
get
{
return _ToDelete;
}
set
{
_ToDelete = value;
PropertyChanged(this, new PropertyChangedEventArgs("ToDelete"));
}
}
public int Edited
{
get
{
return _Edited;
}
set
{
_Edited = value;
PropertyChanged(this, new PropertyChangedEventArgs("Edited"));
}
}
}
Take note of the comments in the constructor. You need to point the PropertyChanged event of each SpecialPriceRow to the UpdateStatuses function in order for this to work properly.
Now all you need to do is bind your labels to the appropriate properties in the ViewModel.
If your SpecialPriceRows list becomes very large, you may want to consider calculating the status counts a bit differently. Currently, it is iterating through the entire list every time one instance is updated. For this to perform better, you may want to keep the old value of the status in the SpecialPriceRow class and every time an update occurs, increment the new status count and decrement the old one.
I'm not aware of any builtin functionality to do this. I would create a custom property in your data context class that does the counting and bind to this.
Something like this:
public int AddedCount
{
get
{
return SpecialPriceRows.Where(r => r.Status == ChangeStatus.Added).Count();
}
}
Then whenever an item is changed or added you need to explicitly raise the property changed for this:
public void AddItem()
{
...
OnPropertyChanged("AddedCount");
}
Then you only need to bind in your XAML code like:
<TextBlock Text="{Binding AddedCount}" />
You may need to subscribe to the change events in your collection to know when an item changes.
Alternative:
You can also create a ListCollectionView with a specific filter and bind to its Count property:
AddedItems = new ListCollectionView(TestItems);
AddedItems.Filter = r => ((SpecialPriceRow)r).Status == ChangeStatus.Added;
In your XAML you would then bind to the Count property of this:
<TextBlock Text="{Binding AddedItems.Count}" />
This has the advantage that it will automatically keep track of added and removed items in the collection and update itself. You have to refresh it manually though when the property of an item changes which affects the filter.