Caliburn Micro, Binding and Notifying

10,749

Solution 1

Firstly, I would start by removing your PlayerList property, and update your PlayerProfiles property like so:

 public BindableCollection<PlayerProfile> PlayerProfiles {
    get { return _playerProfiles; }
    set { 
        _playerProfiles = value;
        NotifyOfPropertyChange(() => PlayerProfiles);
    }
}

Next, I would not call your view model properties something which describes how they are rendered, i.e. change TB_AddPlayer to just AddPlayer (or better still something like PlayerName as that's what it is).

Next, if you call your ComboBox PlayerProfiles, then the binding should automatically happen via convention. In your AddPlayer method, you want to add the new player profile via your property, not the backing field:

Change:

public void AddPlayer() {
     _playerProfiles.Add(new PlayerProfile(TB_AddPlayer));
     Test = "Pressed";
     NotifyOfPropertyChange(() => PlayerProfiles);
}

to:

public void AddPlayer() {
     this.PlayerProfiles.Add(new PlayerProfile(this.PlayerName));
}

Because PlayerProfiles is a BindableCollection, change notifications will be performed, and your ComboBox will be updated.

Also, I would always explicitly specify the access modifier for your backing fields, i.e. private string _playerName; not just string _playerName;

Try these changes, and update your code in the question if you still have problems.

Solution 2

I think I got the answers

On your text box use updatesourcetrigger=property changed The Can Execute is not updating cause you didn't use the depends on (or dependencies dont remeber the name) attribute.

The combobox not updating cause you should bind it to a bindable collection.

Share:
10,749
EluciusFTW
Author by

EluciusFTW

Updated on June 18, 2022

Comments

  • EluciusFTW
    EluciusFTW almost 2 years

    After a couple of months of hands-on WPF I decided to give Caliburn Micro a shot. I have several things I can't get to work. I'll post the code first, see my questions/problems below:

    public class MainViewModel : PropertyChangedBase
    {
        BindableCollection<PlayerProfile> _playerProfiles = staticInfos.Load();
    
        public BindableCollection<PlayerProfile> PlayerProfiles {
            get { return _playerProfiles; }
            set { 
                _playerProfiles = value;
                NotifyOfPropertyChange(() => PlayerList);
            }
        }
    
        string _tb_AddPlayer;
        public string TB_AddPlayer {
            get { return _tb_AddPlayer; }
            set { 
                _tb_AddPlayer = value;
                NotifyOfPropertyChange(() => TB_AddPlayer);
                NotifyOfPropertyChange(() => CanAddPlayer);
            }
        }
    
        List<string> _pl = new List<string>(); 
        public List<string> PlayerList {
            get{
                foreach (PlayerProfile p in PlayerProfiles) {
                    if (!_pl.Contains(p.Name)) _pl.Add(p.Name);
                }
                return _pl;               
            }
            set {
                _pl = value;
                NotifyOfPropertyChange(()=>PlayerList);
            }
        }
    
        // Dummy Test String
        string _test;
        public string Test { 
            get { return _test; } 
            set { 
                _test = value; 
                NotifyOfPropertyChange(() => Test); 
            }
        }
    
        // Button Action
        public void AddPlayer() {
             _playerProfiles.Add(new PlayerProfile(TB_AddPlayer));
             Test = "Pressed";
             NotifyOfPropertyChange(() => PlayerProfiles);
        }
    
        public bool CanAddPlayer {
            get { return true; }
            // doesnt work: get { return !string.IsNullOrWhiteSpace(TB_AddPlayer); }
        }
    }
    

    So here goes: I use the binding convention, i.e., a Property in the MainView should bind to the Element of same Name in the View.

    • First of all, notice that PlayerList is just a list of the names of the Players in PlayerProfiles, which I created because when binding to a Combobox, they don't show up when I name the Combobox PlayerProfiles, yet they do when I go via the list and name it PlayerList - although I have overridden the ToString method in the PlayerProfile class. Why? This seems clumsy... (The staticInfos.Load() method populates the PlayerProfiles with several dummy names, if you are wondering...)

    • When I type something in the Textbox (called TB_AddPlayer) and press the button (called AddPlayer) the Test string appears, so I know the action took place, but the new name does not appear in the combobox

    • Also, If I use the commented line in the CanAddPlayer Property, the button never is enabled, not if I start typing, nor when the textbox looses focus with text in it. Why?

    Sorry for these basic questions, I have stared at the 'Hello World' caliburn example for hours and don't see what I am missing... Thx. in advance!

    Edit: Here's the .xaml

    <UserControl x:Class="MixGameM8_MVVM.Views.MainView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MixGameM8_MVVM.Views">
    
    <UserControl.Resources>
        <Style TargetType="TextBlock" x:Key="TB_A">
            <Setter Property="Margin" Value="5,5,5,5" />
            <Setter Property="HorizontalAlignment" Value="Center" />
            <Setter Property="FontSize" Value="15" />
        </Style>
    
        <!-- ... Some more of this kind -->
    </UserControl.Resources>
    
    <Grid>
        <!-- ... Grid definitions -->
        <Border Style="{StaticResource Border_A}" Grid.Row="0" Grid.Column="0">
            <StackPanel Name="SP_Controls">
                <TextBlock Style="{StaticResource TB_A}" Text="Controls"/>
                <ComboBox Style="{StaticResource CB_A}" Name="PlayerProfiles"/>
                <StackPanel Orientation="Horizontal">
                    <TextBox Style="{StaticResource TBox_A}" Name="TB_AddPlayer" MinWidth ="100" />
                    <Button Style="{StaticResource Button_AddPlayer}" Content="Add Player" Name="AddPlayer" />
                 </StackPanel>
            </StackPanel>
        </Border>
    
    <!-- ... And some more cells in the grid, one containing the dummy textbox -->
    
    </Grid>
    </UserControl>
    
  • EluciusFTW
    EluciusFTW over 11 years
    Thanks for your answer! When I bind directly to 'PlayerProfiles', though (and I did the changes in notifications), the Combobox does not show names, but twice (there are two Profiles generated by the Load() method) the entry 'Cannot find view for 'Project_MVVM.Models.PlayerProfile'. What does that mean?
  • EluciusFTW
    EluciusFTW over 11 years
    also, just to be sure, I thought that if I write 'string a...' and 'private string a... ' that they are compiled exactly in the same way, i.e., omitting the prefix automatically sets it to private, or am I wrong?
  • devdigital
    devdigital over 11 years
    You are correct, but it is better practice to be explicit and add the 'private' access modifier. In terms of your first problem, by default Caliburn.Micro will assume that the type in your collection is a view model, and will try to find the associated view for that (e.g. a PlayerProfile.xaml), and it will bind your PlayerProfile type to the PlayerProfile view). However, as this isn't a view model type, you could either set the 'DisplayMemberPath' property of the ComboBox to a property of your PlayerProfile type, or create a ComboBox ItemTemplate.
  • devdigital
    devdigital over 11 years
    See stackoverflow.com/questions/883626/… for an example of an ItemTemplate. You would use this if you want to display more than a string in your ComboBox.
  • EluciusFTW
    EluciusFTW over 11 years
    So Caliburn does not use the ToString Method by default when displaying objects, good to know! I guess instead of a template I could also create a new .xaml 'containing that template' but being a view for the PlayerProfile model?
  • devdigital
    devdigital over 11 years
    Yes you could if you have some complex display needs, but really it would make more sense in that case that your PlayerProfile is a view model type (i.e. PlayerProfileViewModel) which you could create from your collection of PlayerProfiles (e.g. using a LINQ projection).
  • devdigital
    devdigital over 11 years
    The update source trigger is set to property changed by default with Caliburn.Micro for all text box instances.
  • EluciusFTW
    EluciusFTW over 11 years
    Still, I don'T get that part to work. Now when pressing the button, I am adding a new player to the Combobox, but no matter what the content of the textbox, the player has no name (i.e., a white line more appears in the combobox choices)... Why?
  • EluciusFTW
    EluciusFTW over 11 years
    Anyone got an Idea, textbox binding still not working :-( ... all is still as in last comment.