Caliburn Micro, Binding and Notifying
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.
EluciusFTW
Updated on June 18, 2022Comments
-
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 inPlayerProfiles
, which I created because when binding to aCombobox
, they don't show up when I name theCombobox PlayerProfiles
, yet they do when I go via the list and name itPlayerList
- although I have overridden theToString
method in thePlayerProfile class
. Why? This seems clumsy... (ThestaticInfos.Load()
method populates thePlayerProfiles
with several dummy names, if you are wondering...)When I type something in the Textbox (called
TB_AddPlayer
) and press the button (calledAddPlayer
) the Test string appears, so I know the action took place, but the new name does not appear in the comboboxAlso, 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 over 11 yearsThanks 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 over 11 yearsalso, 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 over 11 yearsYou 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 over 11 yearsSee 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 over 11 yearsSo 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 over 11 yearsYes 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 over 11 yearsThe update source trigger is set to property changed by default with Caliburn.Micro for all text box instances.
-
EluciusFTW over 11 yearsStill, 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 over 11 yearsAnyone got an Idea, textbox binding still not working :-( ... all is still as in last comment.