0

I have WPF application with .NETCore 7 and when it load I have view and viewmodel

I generate a list of buttons using code in viewmodel as following

 public partial class DataViewModel : ObservableObject, INavigationAware
    {
        private bool _isInitialized = false;

        [ObservableProperty]
        private ObservableCollection<Scene> _scenes;


        public void OnNavigatedTo()
        {
            if (!_isInitialized)
                InitializeViewModel();
        }

        public void OnNavigatedFrom()
        {
        }

        private void InitializeViewModel()
        {
            var scenesCollection = new ObservableCollection<Scene>();

            for (int i = 0; i < 20; i++)
            {
                string name = "Scene " + (i + 1);
                var scene = new Scene
                {
                    Name = name,
                    Color = new SolidColorBrush(Color.FromArgb(200, 231, 129, 4)),
                    Icon = SymbolRegular.Play48,
                    IsPlaying = false
                };
                scene.PlayStopCommand = new RelayCommand(() => PlayStop(scene));
                scenesCollection.Add(scene);
            }

            Scenes = scenesCollection;

            _isInitialized = true;
        }

        [RelayCommand]
        public void PlayStop(Scene scene)
        {
            var random = new Random();
            if (scene.IsPlaying)
            {
                scene.Icon = SymbolRegular.Play48;
                scene.IsPlaying = false;
            }
            else
            {
                scene.Icon = SymbolRegular.Pause48; // Change the icon to the desired value
                scene.IsPlaying = true;
            }
            scene.Color = new SolidColorBrush(Color.FromArgb(
                (byte)200,
                (byte)random.Next(0, 250),
                (byte)random.Next(0, 250),
                (byte)random.Next(0, 250)));
        }
    }

Structure for scene are as followed

 public struct Scene
    {
        public int ID { get; set; }

        public string Name { get; set; }

        public Brush Color { get; set; }

        public RelayCommand PlayStopCommand { get; set; }

        public SymbolRegular Icon { get; set; }

        public bool IsPlaying { get; set; }
    }

XMAL code

<ui:VirtualizingItemsControl
            Foreground="{DynamicResource TextFillColorSecondaryBrush}"
            ItemsSource="{Binding ViewModel.Scenes, Mode=OneWay}"
            VirtualizingPanel.CacheLengthUnit="Item">
            <ItemsControl.ItemTemplate>
                <DataTemplate DataType="{x:Type models:Scene}">
                    <ui:Button
                        x:Name="buttin"
                        Width="600"
                        Height="50"
                        Margin="2"
                        Padding="0"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        Appearance="Secondary"
                        Background="{Binding Color, Mode=OneWay}"
                        Command="{Binding PlayStopCommand}"
                        Content="{Binding Name, Mode=TwoWay}"
                        FontFamily="Arial"
                        FontSize="25"
                        FontWeight="Bold"
                        Foreground="White"
                        Icon="{Binding Icon, Mode=TwoWay}"
                        IconFilled="True" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ui:VirtualizingItemsControl>

The buttons do initialize correctly, however, when I click on any buttons nothing changes.

I even tried to use Breakpoints and I found out that every time I call the PlayStopCommand scene.IsPlaying is always false and does not change, so this means that the code somehow changes another temporary scene, I'm not sure.

2 Answers2

1

Your problem is in the mechanism you use to identify the button/scene that has been clicked:

In scene.PlayStopCommand = new RelayCommand(() => PlayStop(scene)); you have a closure for the local scene variable. This means that when the command is executed, the actual value (at that moment in time) for scene is used. But at that moment scene does no longer exist: it was locally scoped to the for-loop iteration.

So you need to find another way to find the exact button that was clicked. There is an easy way to do this. e.g.:

  1. refactor your command to accept a string-parameter:

    public RelayCommand<string> PlayStopCommand { get; set; }

  2. pass the Name property of the scene as a CommandParameter:

     Command="{Binding PlayStopCommand}"
     CommandParameter="{Binding Name"
    
  3. refactor the command implementation to accept the name as parameter (and identify the clicked button from that):

    scene.PlayStopCommand = new RelayCommand((name) => PlayStop(name));
    

with the adapted PlayStopmethod:

   public void PlayStop(string name)
   {
      // find the right scene in the _scenes collection
      var scene = _scenes.First((s) => s.Name == name); 
      
      var random = new Random();
      if (scene.IsPlaying)
      ...
            

Finally: if you want any changes in the scene properties to be reflected in your UI, you need to notify the view of the change (your _scenes' collection is observable, but the properties of a scene are not in the code you posted).

Edit: that last part is dealt with in your own answer...

Johan Donne
  • 3,104
  • 14
  • 21
0

I ended up changing the Scene structure to ObservableObject

   public partial class Scene : ObservableObject
{
    [ObservableProperty]
    public int _iD;

    [ObservableProperty]
    public string? name;

    [ObservableProperty]
    public Brush? color;

    [ObservableProperty]
    public RelayCommand? playStopCommand;

    [ObservableProperty]
    public SymbolRegular icon;

    [ObservableProperty]
    public bool isPlaying;
}