0

I have copied ObservableStack from Stackoverflow answer.

public class ObservableStack<T> : Stack<T>, INotifyCollectionChanged, INotifyPropertyChanged
    {
        public ObservableStack()
        {
        }

        public ObservableStack(IEnumerable<T> collection)
        {
            foreach (var item in collection)
                base.Push(item);
        }

        public ObservableStack(List<T> list)
        {
            foreach (var item in list)
                base.Push(item);
        }


        public new virtual void Clear()
        {
            base.Clear();
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        public new virtual T Pop()
        {
            var item = base.Pop();
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
            return item;
        }

        public new virtual void Push(T item)
        {
            base.Push(item);
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
        }


        public virtual event NotifyCollectionChangedEventHandler CollectionChanged;


        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            this.RaiseCollectionChanged(e);
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            this.RaisePropertyChanged(e);
        }


        protected virtual event PropertyChangedEventHandler PropertyChanged;


        private void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (this.CollectionChanged != null)
                this.CollectionChanged(this, e);
        }

        private void RaisePropertyChanged(PropertyChangedEventArgs e)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, e);
        }


        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
        {
            add { this.PropertyChanged += value; }
            remove { this.PropertyChanged -= value; }
        }
    }

And now I am trying to Bind the visibility of button to Stack.Count by using following Converter class:

public class StackToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            var collection = (Stack<int>)value;
            return collection.Count > 0  ? Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

I defined stack in code behind as a public property

public ObservableStack<int> myStack;

public MainPage()
{
            myStack = new ObservableStack<int>();
            this.InitializeComponent(); 
            myButton.DataContext = myStack;
}

and XAML is as follow:

 <Page.Resources>
        <Converters:StackToVisibilityConverter x:Key="StackToVisibilityConverter"/>
    </Page.Resources>


<Button x:Name="myButton" Content="Test Button" Visibility="{Binding Converter={StaticResource StackToVisibilityConverter}}"/>

Now when I push an int using another button click event, the button is still invisible, what am I doing wrong?

Community
  • 1
  • 1
iamsharp
  • 39
  • 6
  • I can't find the code where it raises `PropertyChanged` for `"Count"` when an item is added or removed (or when anything else happens). I'm wondering if anything on Stack is going to call your OnPropertyChanged; when you set a breakpoint there, do you ever hit it? The `new` stuff is questionable as well. I'd flush this mess and write my own observable stack, probably derived from ObservableCollection. Either that or I'd just write Push and Pop extension methods for ObservableCollection and go out for a beer. – 15ee8f99-57ff-4f92-890c-b56153 Jun 08 '16 at 16:06

3 Answers3

1

The ObservableStack advertises when it's contents change, but you have nothing advertising that the stack itself has changed. And since your binding is to the stack itself, you would need to advertise that.

Unfortunately, I'm not entirely certain how you can advertise that the DataContext object has been modified; I've only done it with properties on the DataContext object. In this case, since you're actually only using the Count property, you could change your converter to

public class StackToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var count = (int)value;
        return count > 0  ? Visibility.Visible : Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

and then update your binding to bind to Count instead of the ObservableStack

<Button x:Name="myButton" Content="Test Button" Visibility="{Binding Path=Count, Converter={StaticResource StackToVisibilityConverter}}"/>

The ObservableStack should be raising the necessary events for this to work

filhit's answer is likely correct that you will have to include RaisePropertyChanged calls for Count. I thought that the CollectionChanged would also do this, but further research seems to indicate not.

Tyler Lee
  • 2,736
  • 13
  • 24
1

The stack itself is not changed to another stack, so the binding is not reevaluated, when you pop or push.

You should bind to Count property instead, and change the converter to convert the count to visibility.

<Button x:Name="myButton" Content="Test Button" Visibility="{Binding Count, Converter={StaticResource GreaterThanEqualIntToVisibilityConverter}}"/>

Converter:

public object Convert(object value, Type targetType, object parameter, string language)
{
    var count = (int)value;
    return count > 0 ? Visibility.Visible : Visibility.Collapsed;
}

And you want the stack to notify that Count changed in the ObservableStack implementation.

protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    this.RaiseCollectionChanged(e);
    RaisePropertyChanged(new PropertyChangedEventArgs("Count"));
}
filhit
  • 2,084
  • 1
  • 21
  • 34
-2

That's because base class Stack doesn't notify "Count" to the binding target. You may manually notify by adding:

RaisePropertyChanged(new PropertyChangedEventArgs("Count"));

inside at the end of OnCollectionChanged method.

Jay Kim
  • 63
  • 3
  • Count is only referenced inside the converter. The binding logic wouldn't care that Count changed since the binding isn't on `Count` – Tyler Lee Jun 08 '16 at 16:11