I'm starting to study about WFM MVVM pattern.
But I can't understand why this Button click works without binding any event or action.
View
<Window x:Class="WPF2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WPF2.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:MainWindowViewModel x:Key="MainViewModel" />
</Window.Resources>
<Grid x:Name="LayoutRoot"
DataContext="{Binding Source={StaticResource MainViewModel}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,7,0,0" Name="txtID" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId}" />
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,35,0,0" Name="txtName" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.Name}" />
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,61,0,0" Name="txtPrice" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.Price}" />
<Label Content="ID" Grid.Row="1" HorizontalAlignment="Left" Margin="12,12,0,274" Name="label1" />
<Label Content="Price" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="12,59,0,0" Name="label2" VerticalAlignment="Top" />
<Label Content="Name" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="12,35,0,0" Name="label3" VerticalAlignment="Top" />
<Button Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="141" Height="23" Margin="310,40,0,0" Content="Update" />
<ListView Name="ListViewProducts" Grid.Row="1" Margin="4,109,12,23" ItemsSource="{Binding Path=Products}" >
<ListView.View>
<GridView x:Name="grdTest">
<GridViewColumn Header="Product ID" DisplayMemberBinding="{Binding ProductId}" Width="100"/>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="250" />
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price}" Width="127" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ViewModel
class MainWindowViewModel : INotifyPropertyChanged
{
public const string ProductsPropertyName = "Products";
private ObservableCollection<Product> _products;
public ObservableCollection<Product> Products
{
get
{
return _products;
}
set
{
if (_products == value)
{
return;
}
_products = value;
RaisePropertyChanged(ProductsPropertyName);
}
}
public MainWindowViewModel()
{
_products = new ObservableCollection<Product>
{
new Product {ProductId=1, Name="Pro1", Price=11},
new Product {ProductId=2, Name="Pro2", Price=12},
new Product {ProductId=3, Name="Pro3", Price=13},
new Product {ProductId=4, Name="Pro4", Price=14},
new Product {ProductId=5, Name="Pro5", Price=15}
};
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
I found this code from a tutorial i've read. When i click on a row from listview, the textbox values are set. And when I edit those values and click a button, the values on listview are updated.
There is no event nor command bound on the button. So how do button click update the values from listview??
I know this is old but I was looking into this scenario and found my answer.
First, in MVVM theory the "code-behind" shouldn't have code all the code should be in the ViewModel class.
So in order to implement button click, you have this code in the ViewModel (besides the INotifyPropertyChanged implementation):
public ICommand ButtonCommand { get; set; }
public MainWindowViewModel()
{
ButtonCommand = new RelayCommand(o => MainButtonClick("MainButton"));
}
private void MainButtonClick(object sender)
{
MessageBox.Show(sender.ToString());
}
Using the class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace <your namespace>.ViewModels
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
if (execute == null) throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter ?? "<N/A>");
}
}
}
And in XAML:
<Window.DataContext>
<viewModels:MainWindowViewModel />
</Window.DataContext> <Button Content="Button" Command="{Binding ButtonCommand}" />
In above sample snippet, ListView is binded to an ObservableCollection of Product which is internally implemented INotfiyPropertyChanged Interface. This interface is responsible for raising PropertyChanged event and updates binded UI element values whenever it change.
More over you can see here, the Text property of all TextBoxes are binded as Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId}"
Binding ElementName - This is a markup extension which will tell XAML compiler to bind your ListView to Textbox control
Path - This will point to a specific property of Binded UI Element. In this case it will point to ListView.SelectedItem object property. ie. Product.ProductID
The binding mode of WPF UI element is TwoWay by default. So it will update both Source and Target whenever the value changes. You can try this by changing mode to OneWay
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,7,0,0" Name="txtID" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId,Mode=OneWay}" />
You need to set UpdateSourceTrigger to Explicit and on the Click event of the Button you need to update the binding source.
The Buttons Click event is executed first before Command. Hence the source properties can be updated in the Click event. I have modified your code to demonstrate this.
Note:
I have written the view model in code behind for simplicity.
I have used the RelayCommand from the MVVMLight library.
1st XAML Change
<Button Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="141" Height="23" Margin="310,40,0,0" Content="Update" Click="Button_Click" Command="{Binding UpdateCommand}"/>
2nd XAML Change
<ListView Name="ListViewProducts" Grid.Row="1" Margin="4,109,12,23" ItemsSource="{Binding Path=Products}" SelectedItem="{Binding SelectedProduct}" >
Code behind
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WPF2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
txtID.GetBindingExpression(TextBox.TextProperty).UpdateSource();
txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
txtPrice.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
public class MainWindowViewModel : INotifyPropertyChanged
{
public const string ProductsPropertyName = "Products";
private ObservableCollection<Product> _products;
public ObservableCollection<Product> Products
{
get
{
return _products;
}
set
{
if (_products == value)
{
return;
}
_products = value;
RaisePropertyChanged(ProductsPropertyName);
}
}
private Product _SelectedProduct;
public Product SelectedProduct
{
get { return _SelectedProduct; }
set
{
_SelectedProduct = value;
RaisePropertyChanged("SelectedProduct");
}
}
private ICommand _UpdateCommand;
public ICommand UpdateCommand
{
get
{
if (_UpdateCommand == null)
{
_UpdateCommand = new RelayCommand(() =>
{
MessageBox.Show( String.Format("From ViewModel:\n\n Updated Product : ID={0}, Name={1}, Price={2}", SelectedProduct.ProductId, SelectedProduct.Name, SelectedProduct.Price));
});
}
return _UpdateCommand;
}
}
public MainWindowViewModel()
{
_products = new ObservableCollection<Product>
{
new Product {ProductId=1, Name="Pro1", Price=11},
new Product {ProductId=2, Name="Pro2", Price=12},
new Product {ProductId=3, Name="Pro3", Price=13},
new Product {ProductId=4, Name="Pro4", Price=14},
new Product {ProductId=5, Name="Pro5", Price=15}
};
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Related
When I click on a row of WPF datagrid, I want to open a new Window that has information about a person from the clicked row (that can be changed) by using binding. How can I do it? And how can I save the changed information?
Thanks in Advance!
I did solve a simelar propblem using the Behaviours Libary.
First Make sure you have the Nuget package: "Microsoft.Xaml.Behaviors.Wpf" installed.
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
<DataGrid>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="MouseUp">
<i:InvokeCommandAction
Command="{Binding OpenWindowCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=SelectedItem}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
Hope this snippet helps.
You can add a SelectedItem property in the ViewModel. Bind that to DataGrid's SelectedItem. Now you know which item the user has selected (clicked on).
In the DataGrid's MouseDown or MouseUp event you can open the new Window with the same ViewModel object as the DataContext. That way the new Window knows which item was selected. Bind the new Window's fields to the SelectedItem in the ViewModel.
If you have set up the INotifyPropertyChanged correctly, the values that are changed in the new Window also will show up in the DataGrid in the first Window.
Since the SelectedItem also is part of the collection, you will automatically have the changed values in the collection as well. (I assume this is what you mean with "save the changed information").
if your collection, that is bound to the DataGrid, is a collection of Person objects, the "SelectedItem property" could look like this:
public Person SelectedPerson
{
get
{
return _selectedPerson;
}
set
{
_selectedPerson = value;
PropertyChanged....
}
}
private Person _selectedPerson;
ViewModel.cs
namespace Procect1
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
ListOfPeople = new ObservableCollection<Person>();
}
string image;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private void Window2OpenExecute()
{
Window2 Window2 = new Window2(this);
Window2.Show();
}
private void AddPhotoExecute()
{
foreach (Window window in Application.Current.Windows)
{
if (window.GetType() == typeof(Window2))
{
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select a picture";
op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png|" +
"JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"Portable Network Graphic (*.png)|*.png";
if (op.ShowDialog() == true)
{
(window as Window2).imgPhoto.Source = new BitmapImage(new Uri(op.FileName));
image = op.FileName;
}
}
}
}
private void AddPersonToListExecute()
{
foreach (Window window in Application.Current.Windows)
{
if (window.GetType() == typeof(Window2))
{
ListOfPeople.Add(new Person()
{
Name = (window as Window2).textBox_FirstName.Text,
Surname = (window as Window2).textBox_LastName.Text,
IdNumber = (window as Window2).textBox_IdNumber.Text,
Age = (window as Window2).textBox_Age.Text,
Image = image
});
(window as Window2).Close();
}
}
}
private void SerializeExecute()
{
Stream stream = File.OpenWrite(Environment.CurrentDirectory + "\\Serialization1.xml");
XmlSerializer people = new XmlSerializer(typeof(ObservableCollection<Person>));
people.Serialize(stream, ListOfPeople);
stream.Close();
}
private void DeSerializeExecute()
{
XmlSerializer deserializer = new XmlSerializer(typeof(ObservableCollection<Person>));
TextReader textReader = new StreamReader(Environment.CurrentDirectory + "\\Serialization1.xml");
ListOfPeople = (ObservableCollection<Person>)deserializer.Deserialize(textReader);
textReader.Close();
}
public ICommand Window2Open { get { return new RelayCommand(Window2OpenExecute); } }
public ICommand AddPersonToList { get { return new RelayCommand(AddPersonToListExecute); } }
public ICommand Serialize { get { return new RelayCommand(SerializeExecute); } }
public ICommand DeSerialize { get { return new RelayCommand(DeSerializeExecute); } }
public ICommand AddPhoto { get { return new RelayCommand(AddPhotoExecute); } }
public ObservableCollection<Person> ListOfPeople
{
get
{
return listOfPeople;
}
set
{
listOfPeople = value;
OnPropertyChanged("ListOfPeople");
}
}
public Person SelectedPerson
{
get
{
return _selectedPerson;
}
set
{
_selectedPerson = value;
OnPropertyChanged(nameof(SelectedPerson));
}
}
private Person _selectedPerson;
private ObservableCollection<Person> listOfPeople;
public event PropertyChangedEventHandler PropertyChanged;
}
}
Person.cs
namespace Procect1
{
public class Person : INotifyPropertyChanged
{
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
public string Surname
{
get
{
return surname;
}
set
{
surname = value;
OnPropertyChanged(nameof(Surname));
}
}
public string IdNumber
{
get
{
return idNumber;
}
set
{
idNumber = value;
OnPropertyChanged(nameof(IdNumber));
}
}
public string Age
{
get
{
return age;
}
set
{
age = value;
OnPropertyChanged(nameof(Age));
}
}
public string Image
{
get
{
return image;
}
set
{
image = value;
OnPropertyChanged(nameof(Image));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private string name;
private string surname;
private string idNumber;
private string age;
private string image;
}
}
MainWindow.xaml
<Window x:Class="Procect1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Procect1"
mc:Ignorable="d"
Title="Lista pacjentów" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<!--<RowDefinition Height="auto" />-->
<!--<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>-->
</Grid.RowDefinitions>
<DataGrid Name="listView" MouseUp="listView_MouseUp" ItemsSource="{Binding ListOfPeople}" AutoGenerateColumns="False" SelectedItem="{Binding Index}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Header="Imię" />
<DataGridTextColumn Binding="{Binding Surname}" Header="Nazwisko" />
<DataGridTextColumn Binding="{Binding IdNumber}" Header="Pesel" />
<DataGridTextColumn Binding="{Binding Age}" Header="Wiek" />
<DataGridTemplateColumn Header="Zdjęcie" Width=" 100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Image}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Canvas>
<Button Canvas.Right="20" Canvas.Top="30" Height="50" Width="80" FontSize="18" Command="{Binding Window2Open}">Dodaj</Button>
<Button Canvas.Right="20" Canvas.Top="100" Height="50" Width="80" FontSize="18" Command="{Binding Serialize}">Serializuj</Button>
<Button Canvas.Right="20" Canvas.Top="170" Height="50" Width="80" FontSize="18" Command="{Binding DeSerialize}">Załaduj</Button>
</Canvas>
</Grid>
</Window>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow( )
{
InitializeComponent();
}
private void listView_MouseUp(object sender, MouseButtonEventArgs e)
{
Window3 window3 = new Window3();
window3.Show();
}
}
Window2.xaml
<Window x:Class="Procect1.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Procect1"
mc:Ignorable="d"
Title="Dane Pacjenta" Height="600" Width="500">
<Canvas>
<Label Canvas.Left="20" Canvas.Top="20" FontSize="24">Imię</Label>
<TextBox x:Name="textBox_FirstName" Canvas.Left="190" Canvas.Top="30" Height="28" Width="200" ></TextBox>
<Label Canvas.Left="20" Canvas.Top="100" FontSize="24">Nazwisko</Label>
<TextBox x:Name="textBox_LastName" Canvas.Left="190" Canvas.Top="110" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="180" FontSize="24">Pesel</Label>
<TextBox x:Name="textBox_IdNumber" Canvas.Left="190" Canvas.Top="190" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="260" FontSize="24">Wiek</Label>
<TextBox x:Name="textBox_Age" Canvas.Left="190" Canvas.Top="270" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="340" FontSize="24">Zdjęcie</Label>
<Image Name="imgPhoto" Canvas.Left="190" Canvas.Top="350" Height="120" Width="150"></Image>
<Button Canvas.Right="20" Canvas.Top="400" FontSize="16" Command="{Binding AddPhoto}" >Dodaj zdjęcie</Button>
<Button Canvas.Left="250" Canvas.Bottom="20" Height="40" Width="80" FontSize="24" Command="{Binding AddPersonToList}">Zapisz</Button>
</Canvas>
</Window>
MainWindow2.cs
public partial class Window2 : Window
{
public Window2(ViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
Window3.xaml
<Window x:Class="Procect1.Window3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Procect1"
mc:Ignorable="d"
Title="Dane Pacjenta" Height="600" Width="500">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Canvas>
<Label Canvas.Left="20" Canvas.Top="20" FontSize="24">Imię</Label>
<TextBox x:Name="textBox_FirstName" Text="{Binding SelectedPerson.Name}" Canvas.Left="190" Canvas.Top="30" Height="28" Width="200" ></TextBox>
<Label Canvas.Left="20" Canvas.Top="100" FontSize="24">Nazwisko</Label>
<TextBox x:Name="textBox_LastName" Text="{Binding SelectedPerson.Surname}" Canvas.Left="190" Canvas.Top="110" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="180" FontSize="24">Pesel</Label>
<TextBox x:Name="textBox_IdNumber" Text="{Binding SelectedPerson.IdNumber}" Canvas.Left="190" Canvas.Top="190" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="260" FontSize="24">Wiek</Label>
<TextBox x:Name="textBox_Age" Text="{Binding SelectedPerson.Age}" Canvas.Left="190" Canvas.Top="270" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="340" FontSize="24">Zdjęcie</Label>
<Image Name="imgPhoto" Source="{Binding SelectedPerson.Image}" Canvas.Left="190" Canvas.Top="350" Height="120" Width="150"></Image>
<Button Canvas.Right="20" Canvas.Top="400" FontSize="16" Command="{Binding AddPhoto}" >Dodaj zdjęcie</Button>
<Button Canvas.Left="250" Canvas.Bottom="20" Height="40" Width="80" FontSize="24">Zapisz</Button>
</Canvas>
</Window>
In Window3.cs I haven't added any code.
RylayCommand.cs
public class RelayCommand : ICommand
{
private readonly Func<bool> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute)
: this(execute, null)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
}
I post it as an answer because I couldn't add it to the question.
On MainWindow you have bound the SelectedItem to "Item". If you look at the Output window (if you are using Visual Studio) when you are running the program, you find following error:
System.Windows.Data Error: 40 : BindingExpression path error: 'Index' property not found on 'object' ''ViewModel'
XAML is a very quite beast, so look there first if the bindings doesn't work.
You should bind the SelectedItem to the SelectedPerson in the ViewModel.
SelectedItem="{Binding SelectedPerson}"
To get the window3 to work, you need to set the DataContext to the main windows DataContext (the view model).
Window3 window3 = new Window3();
window3.DataContext = this.DataContext;
window3.Show();
I am attempting to make a simple GUI for a console application I wrote for importing images into a database. I have a xml file that contains the different studies that the images belong to. I am populating a ListBox with the name of these studies. I have a class file named DirectoryNavigator.cs, depending on the name picked from the ListBox, this class runs processes on files within that directory. I would like to know how I can pass the Selected Value from the ListBox to the DirectoryNavigator.cs class file.
here is my WPF window .xaml
<Window x:Class="APPIL_Importer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:APPIL_Importer"
mc:Ignorable="d"
Background="Gold"
Title="APPIL Importer" Height="385" Width="600">
<Grid Style="{StaticResource gridBackground}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.Resources>
<XmlDataProvider x:Key="StudiesDataSource" Source="/Data/StudyData.xml" XPath="Studies" />
<DataTemplate x:Key="studyName">
<Label Content="{Binding XPath=#Name}"/>
</DataTemplate>
</Grid.Resources>
<Label Grid.Column="1" Grid.Row="0" Style="{StaticResource homeTitle}">DICOM IMPORTER</Label>
<Image x:Name="appil_logo" Grid.Column="0" Grid.Row="0" Grid.RowSpan="4" Source="/Images/appil_logo.png"/>
<Border Grid.Column="1" Grid.Row="0" Style="{StaticResource studyListHeader}" Width="Auto">
<Label Style="{StaticResource studyListText}">Active Studies</Label>
</Border>
<ListBox Name="activeStudiesListBox" Grid.Column="1" Grid.Row="2"
ItemsSource="{Binding Source={StaticResource StudiesDataSource}, XPath=Study}"
ItemTemplate="{StaticResource studyName}">
</ListBox>
<Button Grid.Row="3" Grid.Column="1" Style="{StaticResource buttonStyle}" Click="Go_To_Study_Importer">Select</Button>
</Grid>
</Window>
here is the xaml.cs file behind the window. I am already capturing the selected value and passing it to another Window, but I don;t know how to do that to just a plain class file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace APPIL_Importer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Go_To_Study_Importer(object sender, RoutedEventArgs e)
{
StudyImporter studyImporter = new StudyImporter(this.activeStudiesListBox.SelectedItem);
studyImporter.Show();
}
}
}
Create MainViewModel class that inherits INotifyPropertyChanged as below.
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Now you have to bind this ViewModel to the DataContext of the View.
Add to your code-behind file of the MainWindow.xaml as below.
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
Now it is bound ViewModel(cs) with View(XAML). Therefore you can bring content of the Control to the cs file.
I would show example.
First Add a Model for each item of the ListBox.
I created as below because I don't know your project structure.
public class ResearchItem
{
public string Name { get; set; }
}
And the add to the ViewModel as below. This is instance for binding with the content of the ListBox.
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<ResearchItem> Items { get; } = new ObservableCollection<ResearchItem>();
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
And then bind it to the ListBox in the XAML file as below.
<ListBox Width="200" Height="100" ItemsSource="{Binding Items}">
</ListBox>
Here Items is collection. And ListBox is control to display collection.
Therefore now ListBox will display the Items of the MainViewModel.
But yet, WPF doesn't know how to display the element of the Items because item type is a user-defined type (ResearchItem). Therefore you have to define how to display it as below.
<ListBox Width="200" Height="100" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Name is property ResearchItem.
Now I will fill data into the Items collection as below.
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<ResearchItem> Items { get; } = new ObservableCollection<ResearchItem>();
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public MainViewModel()
{
this.Items.Add(new ResearchItem() { Name = "The first research" });
this.Items.Add(new ResearchItem() { Name = "The second research" });
this.Items.Add(new ResearchItem() { Name = "The third research" });
}
}
When you execute this, you would see what filled data in the ListBox.
Now everything is ready.
I can tell what you want.
To bring selected data of the ListBox, I will added one property as below.
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<ResearchItem> Items { get; } = new ObservableCollection<ResearchItem>();
private ResearchItem selectedItem;
public ResearchItem SelectedItem
{
get => this.selectedItem;
set
{
if (this.selectedItem == value) return;
this.selectedItem = value;
this.OnPropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public MainViewModel()
{
this.Items.Add(new ResearchItem() { Name = "The first research" });
this.Items.Add(new ResearchItem() { Name = "The second research" });
this.Items.Add(new ResearchItem() { Name = "The third research" });
}
}
Now you have to bind added SelectedItem property to the property(SelectedItem) of the ListBox.
The code is as shown below.
<ListBox Width="200" Height="100" ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Everything is over. now you could bring the selected element of the ListBox to the cs file.
The reason we have to do this is to separate design and logic.
I think you should learn the MVVM pattern because it is essential in the WPF.
I hope this helps you.
Okay so I've now got a more specific question.
I'm trying to figure out how I can change the value (bool) of a label when two Textboxes are no longer empty. I cannot seem to figure out how to get it to work even though it seems very straight forward.
Could someone point me in the right direction?
Please see below my code.
Model (Person.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PracticeUI.Model
{
public class Person
{
private string _firstName;
private string _lastName;
public string FullName
{
get
{
return _firstName + " " + _lastName;
}
set { }
}
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
}
}
public string LastName
{
get
{
return _lastName;
}
set
{
_lastName = value;
}
}
}
}
ViewModel (PersonViewModel.cs)
using PracticeUI.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace PracticeUI.ViewModel
{
public class PersonViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Person _newPerson = new Person();
private ICommand _addPerson;
public Person NewPerson
{
get
{
return _newPerson;
}
set
{
_newPerson = value;
OnPropertyChanged("NewPerson");
}
}
public PersonViewModel()
{
_PersonList.Add(new Person() { FirstName = "Tom", LastName = "Barratt" });
_PersonList.Add(new Person() { FirstName = "Harriet", LastName = "Hammond" });
}
private ObservableCollection<Person> _PersonList = new ObservableCollection<Person>();
public ObservableCollection<Person> PersonList
{
get
{
return _PersonList;
}
set
{
_PersonList = value;
OnPropertyChanged("PersonList");
OnPropertyChanged("AddPersonCanExecute");
}
}
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public ICommand AddPersonCommand
{
get
{
if (_addPerson == null)
{
_addPerson = new RelayCommand(p => this.AddPersonCanExecute, p => this.AddPerson());
}
return _addPerson;
}
}
public bool AddPersonCanExecute
{
get
{
return _newPerson.FirstName != string.Empty || _newPerson.LastName != string.Empty;
}
}
public void AddPerson()
{
_PersonList.Add(new Person() { FirstName = _newPerson.FirstName, LastName = _newPerson.LastName });
OnPropertyChanged("PersonList");
}
}
}
View (MainWindow.xaml)
<Window x:Class="PracticeUI.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ViewModel="clr-namespace:PracticeUI.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ViewModel:PersonViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Source={StaticResource ViewModel}, Path=PersonList}" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" Height="200" Margin="0 0 0 20">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding FirstName}"/>
<Label Content="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Label Content="First Name:" Grid.Row="2" Grid.Column="1"/>
<TextBox Text="{Binding Source={StaticResource ViewModel}, Path=NewPerson.FirstName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Grid.Row="2" HorizontalAlignment="Left" Grid.Column="2" Height="40" Width="200" Margin="10 5"/>
<Label Content="First Name:" Grid.Row="3" Grid.Column="1"/>
<TextBox Text="{Binding Source={StaticResource ViewModel}, Path=NewPerson.LastName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Grid.Row="3" HorizontalAlignment="Left" Grid.Column="2" Height="40" Width="200" Margin="10 5"/>
<Button Command="{Binding Source={StaticResource ViewModel}, Path=AddPersonCommand}" Content="Add Person" Width="120" Height="30" Grid.Row="4" Grid.Column="2"/>
<Label Content="{Binding Source={StaticResource ViewModel}, Path=AddPersonCanExecute, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" Grid.Row="2" Grid.Column="4"/>
</Grid>
</Window>
First, rename PersonViewModel to MainViewModel. It's not a viewmodel that represents a person, it's your main viewmodel for the whole program. It has a whole collection of Person; how is it one person? It isn't. Naming your classes well makes it much easier to keep track of what's what. We'll be renaming Person to PersonViewModel because it needs to be a viewmodel also, and it does actually represent a person.
You want the UI to look at the value of AddPersonCanExecute whenever there's a change in the value of NewPerson.FirstName or NewPerson.LastName.
What can cause those values to change?
One way is that NewPerson can change. So:
public Person NewPerson
{
get
{
return _newPerson;
}
set
{
_newPerson = value;
OnPropertyChanged(nameof(AddPersonCanExecute));
OnPropertyChanged(nameof(NewPerson));
}
}
Another way is that the user can type a new value into the textboxes bound to the FirstName and LastName properties of NewPerson. Then you and the UI are out of luck, because Person isn't a viewmodel. It never raises any events when its properties change. So make it a viewmodel.
public class ViewModelBase : INotifyPropertyChanged
{
// Copy your INotifyPropertyChanged implementation here from your main viewmodel
// Make your main viewmodel inherit from ViewModelBase
}
// Formerly PersonViewModel
public class MainViewModel : ViewModelBase
{
// We need this to be the actual type because we'll need to be calling
// RaiseCanExecuteChanged() on it. Or whatever equivalent.
private RelayCommand _addPerson;
// All the stuff PersonViewModel had.
// Stuff
// Stuff
// Stuff
}
// Remember, your old PersonViewModel is now named MainViewModel. This is the class
// that you used to call Person.
public class PersonViewModel : ViewModelBase
{
public string FullName
{
get
{
return _firstName + " " + _lastName;
}
// No empty set, not ever. Somebody will try to set FullName and the compiler
// will let him think it worked. But nothing will change. That's a bug.
//set { }
}
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
// Do the same for LastName. Careful you don't pass nameof(FirstName)
// over there.
OnPropertyChanged(nameof(FirstName));
OnPropertyChanged(nameof(FullName));
}
}
Now the UI knows when those properties change, but the main viewmodel still doesn't. But now that we have notifications from Person, that's solvable. We have to rewrite NewPerson again:
private PersonViewModel _newPerson = null;
public PersonViewModel NewPerson
{
get { return _newPerson; }
set
{
if (value != _newPerson)
{
// Take the handler off the old NewPerson, if any.
if (_newPerson != null)
{
_newPerson.PropertyChanged -= NewPerson_PropertyChanged;
}
_newPerson = value;
if (_newPerson != null)
{
_newPerson.PropertyChanged += NewPerson_PropertyChanged;
}
OnPropertyChanged(nameof(NewPerson));
OnPropertyChanged(nameof(AddPersonCanExecute));
// I don't know what your RelayCommand class looks like, but it should
// provide some way to force it to raise its CanExecuteChanged event.
// That's what the Button is waiting for to enable or disable itself.
_addPerson.RaiseCanExecuteChanged()
}
}
}
private void NewPerson_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Person.FirstName):
case nameof(Person.LastName):
OnPropertyChanged(nameof(AddPersonCanExecute));
AddPerson
break;
}
}
Another point: Don't make your viewmodel a resource. It's not breaking your code, but it serves no purpose and creates extra work for you.
<Window.DataContext>
<ViewModel:PersonViewModel />
</Window.DataContext>
<Window.Resources>
<!-- remove it from here -->
</Window.Resources>
Now for all controls belonging to the Window itself, all your bindings can look like this:
<TextBox
Text="{Binding NewPerson.FirstName, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="2"
HorizontalAlignment="Left"
Grid.Column="2"
Height="40" Width="200" Margin="10 5"
/>
Get rid of Mode=TwoWay on TextBox.Text; that property will cause bindings on it to be TwoWay by default. Keep UpdateSourceTrigger=PropertyChanged only on TextBox.Text: That will cause the textbox to update the viewmodel on every keystroke, instead of the default behavior of updating the viewmodel property only when the textbox loses focus. You don't need UpdateSourceTrigger=PropertyChanged on the command binding or the Label.Content binding, because those properties cannot ever update the viewmodel property. They're OneWay by default, and by the nature of what they do.
I would like to get content from my combobox. I already tried some ways to do that, but It doesn't work correctly.
This is example of my combobox:
<ComboBox x:Name="cmbSomething" Grid.Column="1" Grid.Row="5" HorizontalAlignment="Center" Margin="0 100 0 0" PlaceholderText="NothingToShow">
<ComboBoxItem>First item</ComboBoxItem>
<ComboBoxItem>Second item</ComboBoxItem>
</ComboBox>
After I click the button, I want to display combobox selected item value.
string selectedcmb= cmbSomething.Items[cmbSomething.SelectedIndex].ToString();
await new Windows.UI.Popups.MessageDialog(selectedcmb, "Result").ShowAsync();
Why this code does not work?
My result instead of showing combobox content, it shows this text:
Windows.UI.Xaml.Controls.ComboBoxItem
You need the Content property of ComboBoxItem. So this should be what you want:
var comboBoxItem = cmbSomething.Items[cmbSomething.SelectedIndex] as ComboBoxItem;
if (comboBoxItem != null)
{
string selectedcmb = comboBoxItem.Content.ToString();
}
I have expanded on my suggestion regarding using models instead of direct UI code-behind access. These are the required parts:
BaseViewModel.cs
I use this in a lot of the view models in my work project. You could technically implement it directly in a view model, but I like it being centralized for re-use.
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Hashtable values = new Hashtable();
protected void SetValue(string name, object value)
{
this.values[name] = value;
OnPropertyChanged(name);
}
protected object GetValue(string name)
{
return this.values[name];
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ComboViewModel.cs
This what you'll bind to make it easy to get values. I called it ComboViewModel because I'm only dealing with your ComboBox. You'll want a much bigger view model with a better name to handle all of your data binding.
public class ComboViewModel : BaseViewModel
{
public ComboViewModel()
{
Index = -1;
Value = string.Empty;
Items = null;
}
public int Index
{
get { return (int)GetValue("Index"); }
set { SetValue("Index", value); }
}
public string Value
{
get { return (string)GetValue("Value"); }
set { SetValue("Value", value); }
}
public List<string> Items
{
get { return (List<string>)GetValue("Items"); }
set { SetValue("Items",value); }
}
}
Window1.xaml
This is just something I made up to demonstrate/test it. Notice the various bindings.
<Window x:Class="SO37147147.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbSomething" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" HorizontalAlignment="Center" MinWidth="80"
ItemsSource="{Binding Path=Items}" SelectedIndex="{Binding Path=Index}" SelectedValue="{Binding Path=Value}"></ComboBox>
<TextBox x:Name="selectedItem" MinWidth="80" Grid.Row="2" Grid.Column="0" Text="{Binding Path=Value}" />
<Button x:Name="displaySelected" MinWidth="40" Grid.Row="2" Grid.Column="1" Content="Display" Click="displaySelected_Click" />
</Grid>
</Window>
Window1.xaml.cs
Here's the code-behind. Not much to it! Everything is accessed through the dataContext instance. There's no need to know control names, etc.
public partial class Window1 : Window
{
ComboViewModel dataContext = new ComboViewModel();
public Window1()
{
InitializeComponent();
dataContext.Items=new List<string>(new string[]{"First Item","Second Item"});
this.DataContext = dataContext;
}
private void displaySelected_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(String.Format("Selected item:\n\nIndex: {0}\nValue: {1}", dataContext.Index, dataContext.Value));
}
}
You can add business logic for populating models from a database, saving changes to a database, etc. When you alter the properties of the view model, the UI will automatically be updated.
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).