Wpf MVVM - Tabbed interface is not working as expected - c#

First: I am new to MVVM and WPF.
I am trying to create a little application with a tabbed user interface. Users can create products and storage locations, using a button which should open a new TabItem.
My code in the view looks like this:
<TabControl ItemsSource="{Binding Workspaces}"
IsSynchronizedWithCurrentItem="True"
Margin="3"
DockPanel.Dock="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
and the View Model is this:
ObservableCollection<WorkspaceViewModel> _workspaces;
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
}
return _workspaces;
}
set
{
_workspaces = value;
}
}
public void AddProduct(object obj)
{
Workspaces.Add(new ProductViewModel());
}
Various other buttons add different ViewModels to the Workspaces Collection.
I have defined multiple Data Template (one for each ViewModel). Here is one:
<DataTemplate DataType="{x:Type vm:ProductViewModel}">
<vw:ProductView />
</DataTemplate>
The WorkspaceViewModel is this:
namespace Inventory.Desktop.ViewModels
{
public abstract class WorkspaceViewModel : INotifyPropertyChanged
{
#region Events and EventHandlers
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
and eg the ProductViewModel
namespace Inventory.Desktop.ViewModels
{
public class ProductViewModel: WorkspaceViewModel
{
private Product _product;
private string _displayName;
public string DisplayName
{
get
{
if (String.IsNullOrEmpty(_displayName))
{
return "Neues Produkt";
} else
{
return _displayName;
}
}
set
{
_displayName = value;
NotifyPropertyChanged("DisplayName");
}
}
#region Public Properties
public Product Product
{
get
{
return _product;
}
set
{
_product = value;
NotifyPropertyChanged("Product");
}
}
public string Title
{
get
{
return _product.Title;
}
set
{
_product.Title = value;
NotifyPropertyChanged("Title");
}
}
public string ScanCode
{
get
{
return _product.ScanCode;
}
set
{
_product.ScanCode = value;
NotifyPropertyChanged("ScanCode");
}
}
public string Manufacturer
{
get
{
return _product.Manufacturer;
}
set
{
_product.Manufacturer = value;
NotifyPropertyChanged("Manufacturer");
}
}
public string ManufacturerNumber
{
get
{
return _product.ManufacturerNumber;
}
set
{
_product.ManufacturerNumber = value;
NotifyPropertyChanged("ManufacturerNumber");
}
}
public string Description
{
get
{
return _product.Description;
}
set
{
_product.Description = value;
NotifyPropertyChanged("Description");
}
}
#endregion
#region Commands
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
return _saveCommand;
}
set
{
_saveCommand = value;
}
}
#endregion
#region Command Executions
public void Save(object obj)
{
using (var db = new InvContext())
{
db.Products.Attach(Product);
db.Entry(Product).State = Product.ProductId == 0 ?
EntityState.Added : EntityState.Modified;
db.SaveChanges();
}
MessageBox.Show("Product saved: " + Product.Title);
}
#endregion
#region Constructors
public ProductViewModel()
{
if (_product == null)
{
_product = new Product();
}
SaveCommand = new RelayCommand(new Action<object>(Save));
}
#endregion
}
}
Here the ProductView.xaml view:
<UserControl x:Class="Inventory.Desktop.Views.ProductView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="450">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button Name="SaveProductButton" Command="{Binding SaveCommand}" Content="Speichern" Margin="3" BorderThickness="0">
</Button>
</StackPanel>
<StackPanel DockPanel.Dock="Top" VerticalAlignment="Stretch">
<Label Content="Scan Code" />
<TextBox Text="{Binding Path=ScanCode}" HorizontalAlignment="Stretch" Margin="3" Padding="3" Height="50" TextAlignment="Right">
<TextBox.Background>
<ImageBrush ImageSource="..\Images\Barcode32.png" AlignmentX="Left" Stretch="None" />
</TextBox.Background>
</TextBox>
<Label Content="Bezeichnung" />
<TextBox Text="{Binding Path=Title, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Hersteller" />
<TextBox Text="{Binding Path=Manufacturer, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Hersteller Nummer" />
<TextBox Text="{Binding Path=ManufacturerNumber, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Beschreibung / Information" />
<TextBox Text="{Binding Path=Description, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
</StackPanel>
</DockPanel>
</UserControl>
and here the code-behind ProductView.xaml.cs:
namespace Inventory.Desktop.Views
{
/// <summary>
/// Interaktionslogik für ProductView.xaml
/// </summary>
public partial class ProductView : UserControl
{
ProductViewModel _productModel = new ProductViewModel();
public ProductView()
{
InitializeComponent();
base.DataContext = _productModel;
}
}
}
What's currently working:
When I click a button, I got a new TabItem displaying the correct view and all commands work correctly.
What's not working:
When I open a TabItem, enter some information, and then I open another TabItem with a different ViewModel, switching the focus to the new TabItem and then back to the original oen, then all entered information are gone (object is null).
When I open a TabItem, enter some information, and then I open another TabItem with the same ViewModel, then both TabItems show the the same information.
When I add a new TabItem, it doesn't get focus.
I am totally lost and I hope you can tell me what I am doing wrong.
Best
Stefan

Have a property on your ViewModel to store the reference to current/selected tab
public WorkspaceViewModel SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
RaisePropertyChanged(() => SelectedTab);
}
}
and bind this to SelectedItem property on TabControl.
<TabControl ItemsSource="{Binding Workspaces}"
SelectedItem="{Binding SelectedTab, Mode=TwoWay}"
Margin="3"
DockPanel.Dock="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
And finally, you want to update SelectedTab property whenever you are adding a new tab. Modify your AddProduct like this:
public void AddProduct(object obj)
{
var workspace = new ProductViewModel();
Workspaces.Add(workspace);
SelectedTab = workspace;
}

Related

Row click event in WPF datagrid using mvvm

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();

WPF Tabcontrol (TabItem Content is not appearing)

I implemented a TabControl with Closable TabItems in my App. For this, I am using a Collection which I fill with the SubMenuItems of MenuItem "Öffne", which are bound to ICommands in the MainViewModel.
So if I click on MenuItem "Open Tab1", then the header of Tab 1 is created, but I can not see any content. The content of the TabItem is shown AFTER I click on the Header of the TabItem. But I want it to be shown directly when the TabItem is "created" without any need of clicking on the header. Closing the TabItems from the "X" button works fine.
I looked at a couple of examples and tried a ContentTemplate, but it didn't work (Maybe I made something wrong?).
I Hope you can tell me what i have done wrong or show me a good example.
Thanks in advance!
Here are my code snippets:
MainWindow.XAML:
<Window.Resources>
<vm:MainViewModel x:Key="viewModel"/>
</Window.Resources>
<TabControl Background="#FFE5E5E5" ItemsSource="{Binding TabControlViews}" SelectedItem="{Binding CurrentTabItem}" Margin="0,21,0,0">
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel Width="120">
<TextBlock Text="{Binding Header}"/>
<Button
Command="{Binding ParameterizedCommand, Source={StaticResource viewModel}}"
CommandParameter="{Binding Header, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
FontFamily="Courier"
FontSize="9"
FontWeight="Bold"
Margin="0,1,0,0"
Padding="0"
VerticalContentAlignment="Bottom"
Width="16" Height="16" />
<ContentPresenter
Content="{Binding Path=DisplayName}"
VerticalAlignment="Center" />
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<!--<TabControl.ContentTemplate>
<DataTemplate>
</DataTemplate>
</TabControl.ContentTemplate>-->
<TabControl.Resources>
<DataTemplate x:Name="test" DataType="{x:Type vm:MenueVM}">
<cu:MenueSearch/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:FieldPointsVM}">
<cu:FieldPointsSearch/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:DataTransferVM}">
<cu:DataTransfer/>
</DataTemplate>
</TabControl.Resources>
</TabControl>
MainWindow.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = (MainViewModel)Resources["viewModel"];
this.DataContext = vm;
}
}
MainViewModel.cs:
public MainViewModel()
{
TabControlViews = new ObservableCollection<BaseViewModel>();
_menueVM = new MenueVM("Menüpunkte", "Menue");
_fieldVM = new FieldPointsVM("Feldpunkte", "FieldPoint");
_dataVM = new DataTransferVM("DatenTransfer", "DataTransfer");
ParameterizedCommand = new RelayCommand(DoParameterizedCommand);
}
private void DoParameterizedCommand(object parameter)
{
if (parameter.ToString() == "App.ViewModel.MenueVM")
{
TabControlViews.Remove(_menueVM);
}
else if (parameter.ToString() == "App.ViewModel.FieldPointsVM")
{
TabControlViews.Remove(_fieldVM);
}
else if (parameter.ToString() == "App.ViewModel.DataTransfer")
{
TabControlViews.Remove(_dataVM);
}
}
private ICommand _parameterizedCommand;
public ICommand ParameterizedCommand
{
get
{
return _parameterizedCommand;
}
set
{
_parameterizedCommand = value;
}
}
private TabItem _propCurrentTabItem;
public TabItem CurrentTabItem
{
get
{
return _propCurrentTabItem;
}
set
{
_propCurrentTabItem = value;
}
}
private ObservableCollection<BaseViewModel> _TabControlViews = new ObservableCollection<BaseViewModel>();
public ObservableCollection<BaseViewModel> TabControlViews
{
get
{
return _TabControlViews;
}
set
{
_TabControlViews = value;
OnPropertyChanged();
}
}
public ICommand OpenMenupunkteCommand
{
get
{
return new BaseCommand(OpenMenuPunkte);
}
}
public ICommand OpenFeldpunkteCommand
{
get
{
return new BaseCommand(OpenFeldpunkte);
}
}
public ICommand OpenDataTransferCommand
{
get
{
return new BaseCommand(OpenDataTransfer);
}
}
private void OpenMenuPunkte()
{
if (!TabControlViews.Contains(_menueVM))
{
TabControlViews.Add(_menueVM);
}
}
private void OpenFeldpunkte()
{
if (!TabControlViews.Contains(_fieldVM))
{
TabControlViews.Add(_fieldVM);
}
}
private void OpenDataTransfer()
{
if (!TabControlViews.Contains(_dataVM))
{
TabControlViews.Add(_dataVM);
}
}
MenueVM.cs
public class MenueVM : BaseViewModel
{
public MenueVM()
{
//Here are some actions done for Data, but I think they are unimportant for this question
}
public MenueVM(string header, string content)
{
Header = header;
Content = content;
}
private string _header;
public string Header
{
get
{
return _header;
}
set
{
_header = value;
}
}
Still time to post an answer?
Try this :
XAML:
<TabControl ItemsSource="{Binding....}" IsSynchronizedWithCurrentItem="True">
<!-- style, template, ... -->
</TabControl>
CS:
//Adding your viewModel to your ObservableCollection<> TabControlViews
TabControlViews.Add(_viewModelToAdd);
ICollectionView collectionView = CollectionViewSource.GetDefaultView(TabControlViews);
if (collectionView != null)
{
collectionView.MoveCurrentTo(_viewModelToAdd);
//And this is because you don't want your tabItem to be selected :
collectionView.MoveCurrentToPrevious();
}
Found in the downloadable DemoMVVMApp here : https://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090030
I've also spent a huge amount of time to solve this problem... ;-)
The problem is that your tab is been created, but it´s not been selected. So, in addition to calling
TabControlViews.Add(_dataVM)
, you should also update your CurrentTabItem
CurrentTabItem = _dataVM;
and bind your TabControl SelectedItem property to your CurrentTabItem, like this:
<TabControl ItemsSource="{Binding TabControlViews}" SelectedItem="{Binding CurrentTabItem}">
Also, if you remove a TabItem and want to get back to the last one, you have to call
CurrentTabItem = TabControlViews.LastOrDefault();

WPF Record didn't update even with the use of INotifyPropertyChanged

I have a collection of Patients which I set in my ComboBox, whenever I ran and test the form, it seems fine, but whenever I update a record or add another one (from another form), the ComboBox doesn't get updated. I can do a remedy to this by using the code behind and IContent interface but I like to reduce the use of code behind as much as possible. Can you pinpoint to me what markup or code that is lacking?
Here is my Grid Markup (I omitted the RowDefinitions and ColumnDefinitions)
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
<businessLogic:PatientMgr x:Key="PatientsViewModel" />
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid" DataContext="{StaticResource ItemsSource}">
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage"
VerticalAlignment="Center"
HorizontalAlignment="Center"
StretchDirection="Both" Stretch="Uniform"
Source="{Binding ElementName=FullNameComboBox, Path=SelectedItem.PictureId, Converter={StaticResource ToImageSourceConverter}, UpdateSourceTrigger=PropertyChanged}"
/>
</Border>
<TextBox x:Name="IdTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.Id, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="1" Visibility="Collapsed"/>
<ComboBox x:Name="FullNameComboBox" Grid.Row="10" Grid.Column="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath = "FullName"
SelectedIndex="0"
SelectionChanged="FullNameComboBox_OnSelectionChanged"
/>
<TextBox x:Name="GenderTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.GenderName, UpdateSourceTrigger=PropertyChanged}" Grid.Row="11" Grid.Column="1" IsReadOnly="True" IsReadOnlyCaretVisible="True"/>
</Grid>
Here is my BusinessLogicLayer
public class PatientMgr :INotifyPropertyChanged
{
#region Fields
private readonly PatientDb _db;
private Patient _entity;
private List<Patient> _entityList;
private ObservableCollection<Patient> _comboBoxItemsCollection;
private Patient _selectedItem;
#endregion
#region Properties
public Patient Entity
{
get { return _entity; }
set
{
if (Equals(value, _entity)) return;
_entity = value;
OnPropertyChanged();
}
}
public List<Patient> EntityList
{
get { return _entityList; }
set
{
if (Equals(value, _entityList)) return;
_entityList = value;
OnPropertyChanged();
}
}
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
public Patient SelectedItem
{
get { return _selectedItem; }
set
{
if (Equals(value, _selectedItem)) return;
_selectedItem = value;
OnPropertyChanged();
}
}
#endregion
#region Constructor
public PatientMgr()
{
_db = new PatientDb();
Entity = new Patient();
EntityList = new List<Patient>();
Parameters = new Patient();
ComboBoxItemsCollection = new ObservableCollection<Patient>(_db.RetrieveMany(Entity));
SelectedItem = ComboBoxItemsCollection[0];
}
#endregion
public List<Patient> RetrieveMany(Patient parameters)
{
return _db.RetrieveMany(parameters);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Because ItemsSource used statically, your ComboBox doesn't get notified when it's source changed. Try using an instance of PatientMgr as your UserControl.DataContext like this:
public partial class YourUserControl: UserControl
{
private PatientMgr PatientsViewModel;
public YourUserControl()
{
InitializeComponent();
PatientsViewModel = new PatientMgr();
DataContext = PatientsViewModel;
}
public void AddComboItem(Patient item)
{
PatientsViewModel.ComboBoxItemsCollection.Add(item);
}
public void UpdateComboItem(Patient item)
{
//your update code
}
After removing static bindings, your XAML should looks like this:
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid"
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage" ...
Since ComboBoxItemsCollection is an ObservableCollection, it notifies UI on add/remove automatically, however you have to write proper update functionality in order to force UI to refresh.
EDIT:
To implement an update method by taking advantage of Editable feature of ComboBox you could follow these steps:
Defined an integer property to track the last valid index (index != -1) of combo-box selected item and bind it to FullNameComboBox.SelectedIndex.
Defined a string property that represents the text value of combo-box selected item and bind it to FullNameComboBox.Text (the binding should trigger on LostFocus so the changes only accepted when user finished typing).
Find and remove ComboBoxItemsCollection.OldItem (find it by last valid index) and insert ComboBoxItemsCollection.ModifiedItem when FullNameComboBox.Text changes. Using remove and insert instead of assigning (OldItem = ModifiedItem;) will force UI to update.
In PatientMgr Code:
public int LastValidIndex
{
get { return _lastIndex; }
set
{
if (value == -1) return;
_lastIndex = value;
OnPropertyChanged();
}
}
public string CurrentFullName
{
get
{
return SelectedItem.FullName;
}
set
{
var currentItem = SelectedItem;
ComboBoxItemsCollection.RemoveAt(LastValidIndex);
currentItem.FullName = value;
ComboBoxItemsCollection.Insert(LastValidIndex, currentItem);
SelectedItem = currentItem;
}
}
In UserControl.Xaml :
<ComboBox x:Name="FullNameComboBox" Grid.Row="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection,
UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem}"
SelectedIndex="{Binding LastValidIndex}"
IsTextSearchEnabled="False"
Text="{Binding CurrentFullName, UpdateSourceTrigger=LostFocus}"
DisplayMemberPath = "FullName"
/>
I don’t like this:
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
Try this:
OnPropertyChanged(“ComboBoxItemsCollection”);
And, are you sure this equals is resolved right?
if (Equals(value, _comboBoxItemsCollection)) return;
Try to debug it …
Your problem has to do with that IsEditable="True" set on the ComboBox. Your bindings work fine for me.
Here is what i've just tried:
Simple Patient model with its FullName property:
public class Patient : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _FullName;
public string FullName
{
get { return _FullName; }
set
{
_FullName = value;
PropertyChanged(this, new PropertyChangedEventArgs("FullName"));
}
}
}
This is the XAML:
<Window x:Class="PatientsStack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PatientsStack"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:PatientsMgr x:Key="PatientsViewModel"/>
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</Window.Resources>
<Grid DataContext="{StaticResource ItemsSource}">
<StackPanel>
<ComboBox x:Name="FullNameComboBox" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
TextSearch.TextPath="FullName"
DisplayMemberPath="FullName"
SelectedItem="{Binding SelectedPatient}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
VerticalAlignment="Top"
IsTextSearchEnabled="False"
SelectedIndex="0">
</ComboBox>
<Button Content="Change name" Command="{Binding ChangeNameCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
<Button Content="Add patient" Command="{Binding AddPatientCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
</StackPanel>
</Grid>
This two pieces right here did the job:
SelectedItem="{Binding SelectedFilter}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
Without the Text binding, the updated worked, but you didn't see it unless you click the drop down.
As you see, i have 2 command to change one existing Patient's FullName or to add a new one. Both work as expected.
Here is the ViewModel for this:
public class PatientsMgr : INotifyPropertyChanged
{
private ObservableCollection<Patient> _ComboBoxItemsCollection;
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get
{
return _ComboBoxItemsCollection;
}
set
{
_ComboBoxItemsCollection = value;
PropertyChanged(this, new PropertyChangedEventArgs("ComboBoxItemsCollection"));
}
}
private Patient _SelectedPatient;
public Patient SelectedPatient
{
get { return _SelectedPatient; }
set
{
_SelectedPatient = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPatient"));
}
}
public ICommand ChangeNameCommand { get; set; }
public ICommand AddPatientCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public PatientsMgr()
{
ComboBoxItemsCollection = new ObservableCollection<Patient>();
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient1" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient2" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient3" });
ChangeNameCommand = new RelayCommand<Patient>(ChangePatientName);
AddPatientCommand = new RelayCommand<Patient>(AddPatient);
}
public void ChangePatientName(Patient patient)
{
patient.FullName = "changed at request";
}
public void AddPatient(Patient p)
{
ComboBoxItemsCollection.Add(new Patient() { FullName = "patient added" });
}
}
I am posting my RelayCommand too, but i am sure you have it defined for your actions:
public class RelayCommand<T> : ICommand
{
public Action<T> _TargetExecuteMethod;
public Func<T, bool> _TargetCanExecuteMethod;
public RelayCommand(Action<T> executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public bool CanExecute(object parameter)
{
if (_TargetExecuteMethod != null)
return true;
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
T tParam = (T)parameter;
if (_TargetExecuteMethod != null)
_TargetExecuteMethod(tParam);
}
}

How to reuse a UserControl in situations where the DataContext is slightly different?

This is my first post on StackOverflow and also my first Question.
I had created a UserControl in WPF with the MVVM Pattern (called Block).
This UserControl should be used in another UserControl, as DataTemplate of a ListBox (called Sector).
The Block Control should be used as standalone and as DataTemplate for different UserControls.
If I set the DataContext in the View of Block, the standalone Version works great, but not as part of the Sector View.
If I don´t set the DataContext in the View of Block, it works in Sector but don’t standalone.
My question is, is it the only way to leave the DataContext in the View of Block and set it in the View I used the Control or the ViewModel?
Here is my Code:
Model of Block:
public class BlockModel : ModelBase
{
#region private Variables
string blockName = "Block";
string blockContent = "00000";
#endregion private Variables
#region Properties
public string BlockName
{
get { return blockName; }
set { blockName = value; NotifyPropertyChange("BlockName "); }
}
public string BlockContent
{
get { return blockContent; }
set { blockContent = value; NotifyPropertyChange("BlockContent"); }
}
#endregion Properties
#region ctor
public BlockModel () { }
#endregion ctor
}
ViewModel of Block:
public class BlockViewModel : ViewModelBase
{
#region private Variables
string charToFill = "0";
DelegateCommand fillWithChar;
BlockModel dataModel = new BlockModel();
#endregion private Variables
#region Properties
public Models. BlockModel DataModel
{
get { return dataModel; }
set { dataModel = value; }
}
public string BlockContent
{
get { return dataModel. BlockContent; }
set
{
if (dataModel. BlockContent != value)
{
dataModel. BlockContent = value;
NotifyPropertyChange ("BlockContent");
}
}
}
public string BlockName
{
get { return dataModel. BlockName; }
set
{
if (dataModel. BlockName != value)
{
dataModel. BlockName = value;
NotifyPropertyChange("BlockName");
}
}
}
public string CharToFill
{
get { return charToFill; }
set
{
if (charToFill != value)
{
charToFill = value;
NotifyPropertyChange ("CharToFill");
}
}
}
public ICommand FillWithChar
{
get
{
if (fillWithChar == null)
fillWithChar = new DelegateCommand(FillText);
return fillWithChar;
}
}
#endregion Properties
#region ctor
public BlockViewModel()
{
}
#endregion ctor
#region Methods
private void FillText()
{
CodingBlockContent = CodingBlockContent.PadLeft(32, charToFill[0]);
}
#endregion Methods
}
View of Block:
<UserControl x:Class="…./Block"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:…/Controls"
mc:Ignorable="d" d:DesignWidth="460" d:DesignHeight="44">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="124" />
<ColumnDefinition Width="301" />
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding BlockName}"
HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" Margin="2,6"/>
<ComboBox FontFamily="Consolas" Grid.Row="00" Grid.Column="1"
Text="{Binding BlockContent, Mode=TwoWay}"
Tag="{Binding BlockName}" Name="cbxBlock" FontSize="14"
HorizontalAlignment="Left" VerticalAlignment="Center" Width="290" Margin="1,4,0,5" IsEditable="True" Height="26">
<ComboBoxItem Content=""></ComboBoxItem>
<ComboBoxItem Content="0000000000000"></ComboBoxItem>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
<TextBox Margin="10,2,0,2" Text="0" Width="24" FontSize="14" Name="tbxChar" MaxLength="1"></TextBox>
<TextBlock Margin="10,2,0,2" Text="auffüllen" VerticalAlignment="Center" FontSize="14"></TextBlock>
<Button Margin="10,2,0,2" Content="Ausführen" Width="100" Command="{Binding FillWithChar}"></Button>
</StackPanel>
</ComboBox>
<TextBlock Grid.Row="0" Width="30" Grid.Column="2" Text="{Binding CodingBlockContent.Length}"
HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" Margin="2,6,0,6" Grid.ColumnSpan="2"/>
</Grid>
Model of Sector:
public class SectorModel
{
#region private Variables
int blockAmount = 4;
string sectorNumber = "Sektor";
int blockBegin = 0;
bool isSectorInUse = false;
#endregion private Variables
#region Properties
public string SectorNumber
{
get { return sectorNumber; }
set { sectorNumber = value;}
}
public int BlockBegin
{
get { return blockBegin; }
set
{
blockBegin = value;
}
}
public bool IsSectorInUse
{
get { return isSectorInUse; }
set { isSectorInUse = value;}
}
public int BlockAmount
{
get { return blockAmount; }
set
{
blockAmount = value;;
}
}
#endregion Properties
public SectorModel()
{
}
}
ViewModel of Sector:
public class SectorViewModel : ViewModelBase
{
#region private Variables
SectorModel dataModel = new SectorModel();
ObservableCollection<BlockViewModel> sectorBlocks = new ObservableCollection<BlockViewModel>();
#endregion private Variables
#region Properties
public SectorModel DataModel
{
get { return dataModel; }
set { dataModel = value; }
}
public ObservableCollection<BlockViewModel> SectorBlocks
{
get { return sectorBlocks; }
set { sectorBlocks = value; NotifyPropertyChange ("SectorBlocks"); }
}
public string SectorNumber
{
get { return "Sektor " + DataModel.SectorNumber; }
set { DataModel.SectorNumber = value; NotifyPropertyChange ("SectorNumber"); }
}
public int BlockBegin
{
get { return DataModel.BlockBegin; }
set
{
DataModel.BlockBegin = value;
SetBlocks();
OnPropertyChanged("BlockBegin");
}
}
public bool IsSectorInUse
{
get { return DataModel.IsSectorInUse; }
set { DataModel.IsSectorInUse = value; NotifyPropertyChange ("IsSectorInUse"); }
}
public int BlockAmount
{
get { return DataModel.BlockAmount; }
set
{
DataModel.BlockAmount = value;
SetBlocks();
NotifyPropertyChange ("CodingBlockAmount");
}
}
#endregion Properties
void SetBlocks()
{
while (SectorBlocks.Count != BlockAmount)
{
SectorBlocks.Add(new BlockViewModel());
}
int begin = BlockBegin;
foreach (BlockViewModel block in SectorBlocks)
{
block.CodingBlockName = "Block " + begin.ToString().PadLeft(2, '0');
block++;
}
}
public SectorViewModel()
{
SetBlocks();
}
}
View of Sector:
<UserControl xmlns:Views="clr-namespace:…/RFIDControls" x:Class="…/Sector"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="473">
<Border BorderBrush="Black" BorderThickness="0,0,0,1">
<Expander Name="expMain" Margin="0,0,4,0">
<Expander.Header>
<Grid Name="grdHeader">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Height="24" Text="{Binding SectorNumber}" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" FontWeight="Bold" Panel.ZIndex="98"/>
<CheckBox Grid.Column="2" VerticalAlignment="Center" IsChecked="{Binding IsSectorInUse}"></CheckBox>
</Grid>
</Expander.Header>
<Grid>
<ListBox ItemsSource="{Binding SectorBlocks}" Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Views:BlockViewModel}">
<Views:Block/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Expander>
</Border>
Thank you.
With kind regards from Germany
Dominik
PS: I hope my English is not so bad as I suppose
This is a common problem for new users of WPF. You have two possible solutions. When using MVVM, you shouldn't need to set the DataContext of the UserControl anywhere. Instead, you can simply add a DataTemplate to your App.xaml to make the pairing:
<DataTemplate DataType="{x:Type ViewModels:BlockViewModel}">
<Views:BlockView />
</DataTemplate>
In this way, whenever you show an instance of your BlockViewModel, the Framework will display the related BlockView instead (like you have done in your SectorView class).
The alternative option is to not use MVVM for the UserControl (use Bindable DependencyPropertys instead), to not set its DataContext internally and to use RelativeSource Bindings on the elements inside instead:
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding DataContext.BlockName,
RelativeSource={RelativeSource AncestorType={
x:Type YourXmlNamespacePrefix:BlockView}}}" HorizontalAlignment="Left"
VerticalAlignment="Center" FontSize="14" Margin="2,6"/>
Using this method, the RelativeSource Binding will look at whatever object is currently set as the DataContext.
Sounds like Block act as an control, which seems participate in other usercontrol or part of Template, in my opinion, mvvm doesn't work in here, you should use Dependency property instead.
Forgive my poor english.

MVVM viewmodel events (commands?)

I have a MVVM setup that creates a View on my MainWindow. I am not sure how to know when a user Clicks on a specific Notification Item inside the View. Where would I add the event, or a command to know when that happens?
here are is my MVVM code :
MainWindow
cs:
NotificationViewModel notificationViewModel = new NotificationViewModel();
notificationViewModel.AddNoticiation(new NotificationModel() { Message = "Error", Name = "Station 21" });
NotificationView.DataContext = notificationViewModel;
xaml:
<notification:NotificationView x:Name="NotificationView" />
NotificationModel
public class NotificationModel : INotifyPropertyChanged
{
private string _Message;
public string Message
{
get { return _Message; }
set
{
if (_Message != value)
{
_Message = value;
RaisePropertyChanged("Message");
}
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (_Name != value)
{
_Name = value;
RaisePropertyChanged("Name");
}
}
}
public string TimeStamp
{
get { return DateTime.Now.ToString("h:mm:ss"); }
}
#region PropertChanged Block
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
}
NotificationViewModel
public class NotificationViewModel
{
private ObservableCollection<NotificationModel> _Notifications = new ObservableCollection<NotificationModel>();
public ObservableCollection<NotificationModel> Notifications
{
get { return _Notifications; }
set { _Notifications = value; }
}
public void AddNoticiation(NotificationModel notification)
{
this.Notifications.Insert(0, notification);
}
}
NotificationView
<Grid>
<StackPanel HorizontalAlignment="Left" >
<ItemsControl ItemsSource="{Binding Path=Notifications}"
Padding="5,5,5,5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="SlateGray"
CornerRadius="4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Path=TimeStamp}" />
<TextBlock Grid.Column="1"
Text="{Binding Path=Name}" />
<TextBlock Grid.Column="2"
Text="{Binding Path=Message}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
There's no real selection mechanism built into an ItemsControl. It would probably solve your problem to switch out your ItemsControl for a ListBox.
If you do that, you can bind to SelectedItem, then handle any changes made to SelectedItem using the PropertyChanged event.
Example:
In your view model's constructor:
PropertyChanged += NotificationViewModel_PropertyChanged;
Add a property to your view model to allow the binding:
private string _selectedNotification;
public string SelectedNotification
{
get { return _selectedNotification; }
set
{
if (_selectedNotification != value)
{
_selectedNotification = value;
RaisePropertyChanged("SelectedNotification");
}
}
}
Finally, add the event handler to your view model:
NotificationViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e))
{
if (e.PropertyName = "SelectedNotification") DoStuff();
}
You may find that you don't even need to hook into PropertyChanged if you just want to update another control in your view based on the selected item in your list box. You can just bind directly to the property within xaml.

Categories

Resources