When the user wants to add a new Reminder, they click the add button on the mainWindow; and once they have added the data, it should display it in a listbox on the main window using an observable collection.
This brings up a new window which brings up options of, at the moment Date and message.
When the user has entered the data, Finish method is called.
The issue is, when the user has finished inputting the data on the new window, I add it to the reminder collection, but it doesn't update on the main window. I am wondering if is a datacontext issue and if I am even going about this the right way?
Thanks for the help.
Add Window:
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class AddWindow : Window, INotifyPropertyChanged
{
private MainWindow mainW;
public AddWindow(MainWindow mW)
{
InitializeComponent();
mainW = mW;
this.Show();
DataContext = this;
}
private void Finish(object sender, RoutedEventArgs e)
{
mainW.Reminders.Add(new Remind(SelectedDate, Message));
this.Close();
}
private DateTime selectedDate = DateTime.Today;
public DateTime SelectedDate
{
get
{
return selectedDate;
}
set
{
if (value != selectedDate)
{
selectedDate = value;
RaisePropertyChange("SelectedDate");
}
}
}
private string message;
public string Message
{
get
{
return message;
}
set
{
if (message != value)
{
message = value;
RaisePropertyChange("Message");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChange(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
Add Xaml
<TextBox Name="Time" HorizontalAlignment="Left" Height="28" Margin="124,60,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="115"/>
<DatePicker SelectedDate="{Binding SelectedDate}" HorizontalAlignment="Left" Height="28" Margin="124,27,0,0" VerticalAlignment="Top" Width="115"/>
<TextBox Text="{Binding Msg}" HorizontalAlignment="Left" Height="58" Margin="123,93,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="144"/>
<Button Content="Finish" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="135,226,0,0" Click="Finish" />
MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private ObservableCollection<Remind> reminders = new ObservableCollection<Remind>();
public ObservableCollection<Remind> Reminders
{
get
{
return reminders;
}
}
private void Add(object sender, RoutedEventArgs e)
{
AddWindow addWindow = new AddWindow(this);
}
}
Mainwindow Xaml:
</MenuItem>
<MenuItem Header="About">
<MenuItem Header="Info"/>
</MenuItem>
</Menu>
<Button Content="New" HorizontalAlignment="Left" Height="26" Margin="6,279,0,0" VerticalAlignment="Top" Width="81" Click="Add" />
<Button Content ="Delete" HorizontalAlignment="Left" Height="26" Margin="87,279,0,0" VerticalAlignment="Top" Width="79" />
<Button Content="Change" HorizontalAlignment="Left" Height="26" Margin="166,279,0,0" VerticalAlignment="Top" Width="73" />
<ScrollViewer Name="Scroller" HorizontalAlignment="Left" Height="235" Margin="0,31,0,0" VerticalAlignment="Top" Width="346">
<ListBox ItemsSource= "{Binding Reminders}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Height="41" Width="293" >
<TextBlock Text="{Binding Path=dateT}"/>
<TextBlock Text="{Binding Path=Msg}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
<Separator HorizontalAlignment="Left" Height="13" Margin="0,266,0,0" VerticalAlignment="Top" Width="362"/>
Remind :
public class Remind : INotifyPropertyChanged
{
public Remind(DateTime dt, string ms)
{
DateT = dt;
Msg = ms;
}
private DateTime datet;
public DateTime DateT
{
get
{
return datet;
}
set
{
if (datet != value)
{
datet = value;
RaisePropertyChange("dateT");
}
}
}
private string msg;
public string Msg
{
get
{
return msg;
}
set
{
if (msg != value)
{
msg = value;
RaisePropertyChange("Msg");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChange(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Change dateT to DateT in your main window
<TextBlock Text="{Binding Path=DateT}"/>
and you are done.
Under the bottom line everthing with the datacontext was ok. Your the 2 wrong property names were missspelled.
Hm, I created a small solution with your code and it just works fine. The main windows's list gets updated right after I click finish. The only small problem is you use the wrong binding in AddWindow to the message. You bind to "Msg" but it should be "Message" in the 3rd line above:
<TextBox Text="{Binding Message}" HorizontalAlignment="Left" Height="58" Margin="123,93,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="144"/>
Code looks fine but I can see one issue in it:
TextBox in AddWindow is binded with Msg but the corresponding property name in code behind is Message. So, textBox is never binded properly and hence new item is added in collection with String.Empty value for Msg.
<TextBox Text="{Binding Msg}" <-- HERE. It should be Message.
However, it should still show a new object in collection on GUI with empty string and DateTime value set on AddWindow even in case of binding failure.
For updated Remind class in question:
One issue in XAML binding where you are binding with field instead of it's wrapper property.
<TextBlock Text="{Binding Path=dateT}"/> <-- HERE, Path name should be DateT.
Related
I have created a program that changes the text in a Label on the first window when you type in a TextBox on the second window. The idea is that I want to open a new window from my first window then on this new I want to type in a name and press a button to have that name be displayed on the first window. Am I making myself understood? Can anyone help me out? Thanks in advance!
The First Window(MainWindow)--
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
var createPlayerWindow = new CreatePlayerWindow();
DataContext = this;
createPlayerWindow.Show();
}
}
Xaml for MainWindow--
<Grid>
<Label x:Name="label2" Content="Name:" HorizontalAlignment="Left" Margin="10,63,0,0" VerticalAlignment="Top"/>
<Label x:Name="lblName" Content="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="90,63,0,0" VerticalAlignment="Top"/>
<Button x:Name="button" Content="Create Player" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="81" Click="button_Click"/>
</Grid>
The Second Window(CreatePlayerWindow)--
public partial class CreatePlayerWindow : Window
{
public CreatePlayerWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
string playerName = txbName.Text;
Player player = new Player(playerName);
this.DataContext = player;
}
}
Xaml for CreatePlayerWindow--
<Grid>
<Label x:Name="label" Content="Name :" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txbName" HorizontalAlignment="Left" Height="23" Margin="63,12,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<Button x:Name="button" Content="Create Player" HorizontalAlignment="Left" Margin="101,129,0,0" VerticalAlignment="Top" Width="88" Click="button_Click"/>
<Label x:Name="label1" Content="Class :" HorizontalAlignment="Left" Margin="10,41,0,0" VerticalAlignment="Top"/>
<ComboBox x:Name="cmbClass" HorizontalAlignment="Left" Margin="63,45,0,0" VerticalAlignment="Top" Width="120"/>
</Grid>
The Model(Player)--
public class Player : ViewModelBase
{
private string _name;
public string Name
{
get
{
return this._name;
}
set
{
this._name = value;
OnPropertyChanged();
}
}
public Player(string name)
{
_name = name;
}
}
The ViewModel(ViewModelBase)--
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Have the main page's data context and the window's data context use the same instantiated VM which will have a Name property they both will use.
Then in xamls bind appropriately to the Name property on the VM which will adhere to the INotifyPropertyChange notification so that when it changes it will update the other item.
Don't forget that on the editing XAML the binding Mode should be TwoWay otherwise the user changes will not be seen.
Your MainWindow needs a ViewModel for its databinding. DataContext = this is useless here. So create a new class with a PlayerName property (with backing field), instanciate it in MainWindow constructor and set the main windows databinding to it. In the setter call OnPropertyChanged after setting the backingfield to value. Bind your label to the PlayerName property.
In your click event, you should create a viewmodel(player-)instance of your createplayerwindow and bind it to the createplayerwindow-instance's datacontext. add the missing databindings in your createplayerwindow to update your viewmodel (twoway). Replace the Show() method invocation in your button click event with ShowDialog(). After ShowDialog invokation call the new properties setter with the viewmodel.name value.
I am new to xaml, WPFs, C# and the MVVM paradigm. I have started with an app based on this example project, in the selected excerpts i want to disable the authenticate button from the LoginPageViewModel after the authenticate button has been clicked(There is no point clicking the button if you are authenticated). I have got command binding working, as well as text control binding between the view and ViewModel. my LoginPageViewModel is based on a abstract class that inherits from INotifyPropertyChanged
The setter AuthenticateButtonEnabled is working, but it is not binding to the isEnabled proprerty on the form. My question is, what could I have missed, and How can i trace the binding between a View and a ViewModel?
the LoginPageView.xaml button:
<Button x:Name="authenticateButton" Content="{x:Static res:Strings.LoginPage_authenticateButton_content}"
Grid.Column="2" Margin="53,4,0,10"
Grid.Row="2" FontSize="16"
IsEnabled="{Binding Path=AuthenticateButtonEnabled}"
Command="{Binding Path=AuthenticateCommand}" HorizontalAlignment="Left" Width="87"/>
the viewModel
private String _username;
private String _responseTextBlock;
private String _linkTextBlockURI;
private String _linkTextBlockText;
private bool _authenticateButtonEnabled;
...
private async void Authenticate()
{
ResponseTextBlock = Strings.LoginPage_responseBlock_content_checking;#this works!
AuthenticateButtonEnabled = false;
return;
}
....
public bool AuthenticateButtonEnabled
{
get { return _authenticateButtonEnabled; }
set { _authenticateButtonEnabled = value; OnPropertyChanged("AuthenticateButtonEnabled"); }
}
// this is in the abstract class.
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
If you want to have both: command and AuthenticateButtonEnabled, then simply check for this property in CanExecute delegate and vise-versa in property setter update command.
Here is implementation with DelegateCommand and some improvements which you may find useful:
bool _isAuthenticateButtonEnabled;
public bool IsAuthenticateButtonEnabled
{
get { return _isAuthenticateButtonEnabled; }
set
{
_isAuthenticateButtonEnabled = value;
OnPropertyChanged();
AuthenticateCommand.Update();
}
}
// the base could class could actually implement this
void OnPropertyChanged([CallerMemberName] string property) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
public DelegateCommand AuthenticateCommand { get; }
// view model constructor
public ViewModel()
{
AuthenticateCommand = new DelegateCommand(o =>
{
... // some actions when command is executed
}, o =>
{
bool somecondition = ...; // some condition to disable button, e.q. when executing command
return somecondition && IsAuthenticateButtonEnabled;
});
}
This will let you to have both: property to enable/disable button, which can be used in binding (to another control, e.g. CheckBox.IsChecked) and command which can have independent condition to disable button when command shouldn't be executed (typically in async command delegate, when it performs a long running command, but for this you may want to check this answer.).
if you bind the command Property of the Button to an ICommand Property in your Viewmodel, then you do NOT need to handle the IsEnabled Property of the Button because its handled by the CanExecute Method of the ICommand implementation.
google for RelayCommand or DelegateCommand
Thanks to the posters for your help, I wanted to share the working solution for others. I used the DelegateCommand, but had to change some parts in the loginPageViewModel to make it work: I also updated the xaml so that the controls were all inactive after a successful authentication.
the loginPage xaml:
<Label x:Name="usernameLabel" Content="{x:Static res:Strings.LoginPage_usernameLabel_content}" HorizontalAlignment="Left" Margin="10,4,0,0" Grid.Row="0" VerticalAlignment="Top" Width="130" FontSize="16" Height="36" Grid.Column="1"/>
<TextBox x:Name="usernameTextBox" Grid.Column="2" Grid.Row="0" TextWrapping="Wrap"
Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding AuthenticateButtonEnabled}"
Margin="10,5,0,6" FontSize="16" HorizontalAlignment="Left" Width="130" TextChanged="usernameTextBox_TextChanged"/>
<Label x:Name="passwordLabel" Content="{x:Static res:Strings.LoginPage_passwordLabel_content}" Margin="10,5,0,0" Grid.Row="1" VerticalAlignment="Top" FontSize="16" Height="36" Grid.RowSpan="2" HorizontalAlignment="Left" Width="130" Grid.Column="1"/>
<PasswordBox x:Name="passwordBox" Grid.Column="2" Margin="10,0,0,9"
PasswordChanged="PasswordBox_PasswordChanged"
IsEnabled="{Binding AuthenticateButtonEnabled}"
Grid.Row="1" FontSize="16" HorizontalAlignment="Left" Width="130"/>
<Button x:Name="authenticateButton" Content="{x:Static res:Strings.LoginPage_authenticateButton_content}"
Grid.Column="2" Margin="53,4,0,10"
Grid.Row="2" FontSize="16"
IsEnabled="{Binding AuthenticateButtonEnabled}"
Command="{Binding Path=AuthenticateCommand}" HorizontalAlignment="Left" Width="87"/>
the loginPageViewModel:
....
private bool _authenticateButtonEnabled;
private DelegateCommand _authenticateCommand;
public bool AuthenticateButtonEnabled {
get { return _authenticateButtonEnabled; }
set
{
_authenticateButtonEnabled = value;
DynamicOnPropertyChanged(); // this is so named to not content with onPropertyChanged defined elsewhere.
AuthenticateCommand.Update();
}
}
...
public DelegateCommand AuthenticateCommand
{
get {
if (_authenticateCommand == null)
{
_authenticateCommand = new DelegateCommand(Authenticate, AuthenticateEnded);
}
return _authenticateCommand;
}
}
private bool AuthenticateEnded(object obj) {
return _authenticateButtonEnabled;
}
private async void Authenticate(object obj)
{
AuthenticateButtonEnabled = false;
ResponseTextBlock = Strings.LoginPage_responseBlock_content_checking;
i3SoftHttpClient _httpClient = new i3SoftHttpClient();
i3SoftUser _i3SoftUser;
AuthenticateCommand.CanExecute(false);
....
// if authentication does not succeed - turn the buttons back on.
AuthenticateCommand.CanExecute(true);
}
and to the Delegate command class i added:
public void Update()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
I have a small program to test out textbox databinding and it works when I change the value in the textbox itself, but when I try to change the value from the code-behind, the variable is updated but the Textbox is not updated. I have looked around but have not been able to find a solution, as most I have seen are for the textbox updating the variable, which is opposite of what I need. Here is the code:
XAML:
<Grid>
<TextBox Text="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="127,37,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<TextBox Text="{Binding LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="127,88,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<TextBlock HorizontalAlignment="Left" Margin="28,40,0,0" TextWrapping="Wrap" Text="First Name" VerticalAlignment="Top"/>
<TextBlock HorizontalAlignment="Left" Margin="28,91,0,0" TextWrapping="Wrap" Text="Last Name" VerticalAlignment="Top"/>
<Button x:Name="Name" Content="Name!" HorizontalAlignment="Left" Margin="145,212,0,0" VerticalAlignment="Top" Width="75" Click="Name_Click"/>
</Grid>
And My Code:
namespace WpfApplication1
{
public class UIControls : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _firstName;
private string _lastName;
protected void Notify(string propertyName)
{
if(this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string FirstName
{
get { return _firstName; }
set
{
if (value != _firstName)
{
_firstName = value;
Notify("FirstName");
}
}
}
public string LastName
{
get { return _lastName; }
set
{
if (value != _lastName)
{
_lastName = value;
Notify("LastName");
}
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
UIControls viewModel = new UIControls();
this.DataContext = viewModel;
}
private void Name_Click(object sender, RoutedEventArgs e)
{
UIControls ui = new UIControls();
ui.FirstName = "Mike";
ui.LastName = "Smith";
}
}
}
Well you are creating new UIControls on every button click. New UIControls is not binded to text box. Try to create private filed that is binded to MainWindow, something like this:
public partial class MainWindow : Window
{
private UIControls viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new UIControls();
this.DataContext = viewModel;
}
private void Name_Click(object sender, RoutedEventArgs e)
{
viewModel.FirstName = "Mike";
viewModel.LastName = "Smith";
}
I have been facing a issue in updating the XAML in windows phone 8... the properties are binded in XAML with the viewModel, propertyChange is triggered and it changes the values of the properties. but the property members in XAML are only updated once at the beginning since then it does not update any thing in XAML... Although the properties continue to change in ViewModel.... the properties belong to a LIST of observation collection and finally Observation Collection is binded to LongListSelector
I have changed the binding Mode to "two Way" but useless i have pasted the code below.
Looking forward for help.
ViewModel:
private string _description;
public string description
{
set
{
_description = value;
RaisePropertyChanged("_description");
}
get
{
return _description;
}
}
private double _progress_bar_Value;
public double progress_bar_Value
{
set
{
_progress_bar_Value = value;
RaisePropertyChanged("_progress_bar_Value");
}
get
{
return _progress_bar_Value; //= ProfileSetting.ProfileTab_DOB;
}
}
private double _Total_Bytes;
public double Total_Bytes
{
set
{
_Total_Bytes = value;
RaisePropertyChanged("_Total_Bytes");
}
get
{
return _Total_Bytes;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
`
>
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,0" Orientation="Vertical"
>
<TextBlock Text="{Binding description}"
FontSize="18"
TextWrapping="Wrap"
Foreground="White" x:Name="Totalsize"
/>
<ProgressBar x:Name="Download_progressBar"
IsIndeterminate="False"
Maximum="100"
Height="10"
Width="400"
Value="{Binding progress_bar_Value}"
Foreground="White"
/>
<TextBlock Text="{Binding Bytes_received}"
FontSize="18"
TextWrapping="Wrap"
Foreground="White"
x:Name="Total_received"
/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>`
Raise Property Changed on the public property not backing field (as commented by #HighCore)
I have below code to bind ListBox data using MVVM. I would like to implment the Command from MVVM, data is binded completely and I don't know why it doesn't work with the Command. I don't receive the message when clicking on the button.
ViewModel
public class BookmarkViewModel : INotifyPropertyChanged
{
public BookmarkViewModel()
{
DataSource ds = new DataSource();
deleteBookmark = new Command(executeCommand) { Enabled = true };
_bk = ds.getBookmarkDetail();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
List<BookmarkDetail> _bk;
public List<BookmarkDetail> Bookmarks
{
get { return _bk; }
set
{
if (_bk != value)
{
_bk = value;
OnPropertyChanged("Bookmarks");
}
}
}
private Command deleteBookmark;
public Command DeleteBookmark
{
get
{
return deleteBookmark;
}
set
{
deleteBookmark = value;
}
}
void executeCommand()
{
System.Windows.MessageBox.Show(_bk[0].SuraName);
}
public class Command : ICommand
{
private readonly Action executeAction;
private bool enabled;
public bool Enabled
{
get
{
return enabled;
}
set
{
if (enabled != value)
{
enabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
}
}
public Command(Action executeAction)
{
this.executeAction = executeAction;
}
public bool CanExecute(object parameter)
{
return enabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
executeAction();
}
}
}
and XAML binding
<ListBox x:Name="lsbBookmarks" FontFamily="./Fonts/ScheherazadeRegOT.ttf#Scheherazade"
FlowDirection="RightToLeft"
Style="{StaticResource ListBoxStyle1}"
ItemsSource="{Binding Bookmarks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="60"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0"
HorizontalAlignment="Stretch">
<TextBlock Padding="20,0,10,0" HorizontalAlignment="Stretch">
<Run FontSize="50" Text="{Binding ArabicText.ArabicAyaNumber}"
FontFamily="./Fonts/KPGQPC.otf#KFGQPC Uthmanic Script HAFS"
Foreground="Blue"/> <Run FontSize="30" Text="{Binding ArabicText.Aya}"/>
</TextBlock>
</StackPanel>
<Button Grid.Column="1" Tag="{Binding ArabicText.ArabicTextID}"
VerticalAlignment="Center"
Height="60" Width="50" HorizontalAlignment="Right"
Content="X" BorderBrush="Red"
Background="Red" BorderThickness="0"
Padding="0" Command="{Binding DeleteBookmark}"></Button>
</Grid>
<Line X1="0" X2="1" Y1="0" Y2="0" Stretch="Fill" VerticalAlignment="Bottom"
StrokeThickness="1" Stroke="LightGray" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Any Ideas, How to implement the Command using MVVM?
Thanks!
If I were you I would:
Move the Command implementation to a separate file or declare it outside the scope of the BookmarkViewModel class.
Use the second option as ig2r suggested. Your binding would look like this:Command="{Binding DataContext.DeleteBookmark, ElementName=lsbBookmarks}" You can also use any other ElementName other than lsbBookmarks that's defined as a parent of the ListBox.
It appears that your DeleteBookmark command is exposed as a property on the BookmarkViewModel class, whereas the actual data context within the DataTemplate used to render individual ListBox items will be an instance of BookmarkDetail. Since BookmarkDetail does not declare a DeleteBookmark command, the binding fails.
To correct this, either:
Define and expose the DeleteBookmark command on the BookmarkDetail class, or
Extend your command binding to tell the binding system where to look for the delete command, e.g., Command="{Binding DataContext.DeleteBookmark, ElementName=lsbBookmarks}" (untested).