NotifyPropertyChanged, view only updates when event bound to command - c#

I am still fairly new to c# and WPF so perhaps this is expected but I find it strange and I can't seem to get around it.
I have a window with an area to input information about an event and a submit button along with some datagrids bound to observable collections. The button is bound to a command on my viewmodel and when a new event is entered I make some changes to the observable collections and the datagrids on the view are updated.
The issue is when a cell on one of the datagrids is edited. I have tried binding the celleditended event to a command on my view model without much success so I am trying it a different way by using the code behind event handler. I pass the edit information I need to the viewmodel make the changes to the observable collections in exactly the same way as it works above and can see the change in the observable collection but the view doesn't update. The notifypropertychange doesn't fire and neither does the notifycollectionchange.
So why does the observablecollection seem to only fire a property or collection change to update the view when the event is through a Command?
Full Code follows:
XAML code for the main window view; and the example here is where I got the xcdg:CellEditorBinding bit from.
<Window.Resources>
<DataTemplate x:Key="DateTextblock">
<TextBlock Text="{Binding StringFormat={}{0:D}}"/>
</DataTemplate>
<xcdg:DataGridCollectionViewSource x:Key="HistoryViewSource" Source="{Binding History, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<xcdg:DataGridCollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Date" Direction="Descending"/>
</xcdg:DataGridCollectionViewSource.SortDescriptions>
</xcdg:DataGridCollectionViewSource>
<ViewModel:MainWindowViewModel x:Key="MainWindowCollections"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<!--Account Balances and Spending goals-->
<RowDefinition Height="Auto"/>
<!--History-->
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<!--Account Balances-->
<ColumnDefinition x:Name="AccountsWidth" Width="Auto"/>
<!--Categorized spending goals-->
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<xcdg:DataGridControl x:Name="AccountsDataGrid"
ItemsSource="{Binding Accounts, UpdateSourceTrigger=PropertyChanged}"
AutoCreateColumns="False"
SelectionMode="Single"
FontSize="14"
Height="Auto"
Width="Auto">
<xcdg:DataGridControl.Columns>
<xcdg:Column Title="Account" FieldName="Name">
<xcdg:Column.CellEditor>
<xcdg:CellEditor>
<xcdg:CellEditor.EditTemplate>
<DataTemplate>
<TextBox x:Name="AccountText" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnSourceUpdated=True}" Style="{StaticResource MainTextboxStyle}" MinWidth="100"/>
</DataTemplate>
</xcdg:CellEditor.EditTemplate>
</xcdg:CellEditor>
</xcdg:Column.CellEditor>
</xcdg:Column>
<xcdg:Column Title="Balance" FieldName="Balance">
<xcdg:Column.CellEditor>
<xcdg:CellEditor>
<xcdg:CellEditor.EditTemplate>
<DataTemplate>
<xctk:DecimalUpDown Value="{Binding Balance, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnSourceUpdated=True}" Style="{StaticResource DecimalUpDownStyle}"/>
</DataTemplate>
</xcdg:CellEditor.EditTemplate>
</xcdg:CellEditor>
</xcdg:Column.CellEditor>
</xcdg:Column>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
</Grid>
<xcdg:DataGridControl Grid.Row="1" Height="Auto" x:Name="HistoryDataGrid" ItemsSource="{Binding Source={StaticResource HistoryViewSource}, UpdateSourceTrigger=PropertyChanged}" AutoCreateColumns="False" SelectionMode="Single" FontSize="14" xcdg:Cell.EditEnded="HistoryDataGrid_EditEnded">
<!--<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand PassEventArgsToCommand="True" Command="{Binding TransactionEditCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>-->
<xcdg:DataGridControl.Columns>
<xcdg:Column Title="ID" FieldName="ID" Visible="False"/>
<xcdg:Column Title="Type" FieldName="Type"/>
<xcdg:Column Title="Amount" FieldName="Amount">
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringFormat={}{0:C}, NotifyOnSourceUpdated=True}"/>
</DataTemplate>
</xcdg:Column.CellContentTemplate>
<xcdg:Column.CellEditor>
<xcdg:CellEditor>
<xcdg:CellEditor.EditTemplate>
<DataTemplate>
<xctk:DecimalUpDown Value="{xcdg:CellEditorBinding NotifyOnSourceUpdated=True}" Style="{StaticResource DecimalUpDownStyle}"/>
</DataTemplate>
</xcdg:CellEditor.EditTemplate>
</xcdg:CellEditor>
</xcdg:Column.CellEditor>
</xcdg:Column>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
</Grid>
The View Code behind file
public partial class MainWindow : Window
{
MainWindowViewModel mainwindowviewmodel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
public void HistoryDataGrid_EditEnded(object sender, RoutedEventArgs e)
{
mainwindowviewmodel.HistoryCellEdit(e);
}
}
The View Model code
public class MainWindowViewModel : GalaSoft.MvvmLight.ObservableObject
{
public MainWindowViewModel()
{
Accounts = DatabaseFunctions.getAccountData();
History = DatabaseFunctions.getHistoryData();
}
private ObservableCollection<AccountsModel> accounts;
public ObservableCollection<AccountsModel> Accounts
{
get { return accounts; }
set
{
accounts = value;
RaisePropertyChanged("Accounts");
}
}
private ObservableCollection<HistoryModel> history;
public ObservableCollection<HistoryModel> History
{
get { return history; }
set
{
history = value;
RaisePropertyChanged("History");
History.CollectionChanged += History_CollectionChanged;
}
}
public ICommand HistoryEditCommand
{
get { return new ParamDelegateCommand<RoutedEventArgs>(HistoryCellEdit); }
}
public void HistoryCellEdit(RoutedEventArgs e)
{
UpdateAccountData("Chequing", 200);
}
public void UpdateAccountData(string AccountName, decimal NewBalance)
{
var item = Accounts.FirstOrDefault(i => i.Name == AccountName);
if (item != null)
{
item.Balance = NewBalance;
}
}
void Accounts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (AccountsModel item in e.NewItems)
item.PropertyChanged += Accounts_PropertyChanged;
if (e.OldItems != null)
foreach (AccountsModel item in e.OldItems)
item.PropertyChanged -= Accounts_PropertyChanged;
}
void History_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (HistoryModel item in e.NewItems)
item.PropertyChanged += History_PropertyChanged;
if (e.OldItems != null)
foreach (HistoryModel item in e.OldItems)
item.PropertyChanged -= History_PropertyChanged;
}
void Accounts_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
void History_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
}
The Accounts Model
public class AccountsModel : ObservableObject
{
private Double id;
public Double ID
{
get { return id; }
set
{
id = value;
RaisePropertyChangedEvent("ID");
}
}
private string name;
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChangedEvent("Name");
}
}
private decimal balance;
public decimal Balance
{
get { return balance; }
set
{
balance = value;
RaisePropertyChangedEvent("Balance");
}
}
}
This History Model
public class HistoryModel : ObservableObject
{
#region Properties
private Double id;
public Double ID
{
get { return id; }
set
{
id = value;
RaisePropertyChangedEvent("ID");
}
}
private string type;
public string Type
{
get { return type; }
set
{
type = value;
RaisePropertyChangedEvent("Type");
}
}
private decimal amount;
public decimal Amount
{
get { return amount; }
set
{
amount = value;
RaisePropertyChangedEvent("Amount");
}
}
}
Notify Property Changed
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Related

Pass ObservableCollection<> type as dependency property

I am trying to create a multi-select Combobox Custom control, This custom control should expose a dependency property called DropDownDataSource through which the user of the control can decide what day should bound to ComboBox. My code looks like this:
MainPage.Xaml
<Grid>
<local:CustomComboBox x:Name="customcb" DropDownDataSource="{x:Bind DropDownDataSource, Mode=OneWay}" Loaded="CustomControl_Loaded"> </local:CustomComboBox>
</Grid>
MainPage.Xaml.cs
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private ObservableCollection<Item> _dropDownDataSource;
public ObservableCollection<Item> DropDownDataSource
{
get => _dropDownDataSource;
set
{
_dropDownDataSource = value;
OnPropertyChanged();
}
}
public MainPage()
{
this.InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private void CustomControl_Loaded(object sender, RoutedEventArgs e)
{
var Items = new ObservableCollection<Item>(Enumerable.Range(1, 10)
.Select(x => new Item
{
Text = string.Format("Item {0}", x),
IsChecked = x == 40 ? true : false
}));
DropDownDataSource = Items;
}
}
Models
public class Item : BindableBase
{
public string Text { get; set; }
bool _IsChecked = default;
public bool IsChecked { get { return _IsChecked; } set { SetProperty(ref _IsChecked, value); } }
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value,
[System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
CustomUserControl XAML
<Grid x:Name="GrdMainContainer">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox Width="200" FontSize="24" Text="{Binding Header, Mode=TwoWay}"
IsReadOnly="True" TextWrapping="Wrap" MaxHeight="200" />
<ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="200" Width="200" Background="White">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}"
FontSize="24"
Foreground="Black"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
IsThreeState="False" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Grid>
CustomUserControl Cs file
public sealed partial class CustomComboBox : UserControl
{
public CustomComboBox()
{
this.InitializeComponent();
}
public ObservableCollection<Item> DropDownDataSource
{
get { return (ObservableCollection<Item>)GetValue(DropDownDataSourceProperty); }
set { SetValue(DropDownDataSourceProperty, value); }
}
public static readonly DependencyProperty DropDownDataSourceProperty =
DependencyProperty.Register("DropDownDataSource", typeof(ObservableCollection<Item>), typeof(CustomComboBox), new PropertyMetadata("", HasDropDownItemUpdated));
private static void HasDropDownItemUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CustomComboBox ucrcntrl)
{
var grd = UIElementExtensions.FindControl<Grid>(ucrcntrl, "GrdMainContainer");
grd.DataContext = ucrcntrl.DropDownDataSource as ObservableCollection<Item>;
}
}
}
All looks good to me, but for some reason, Dropdown is coming empty. Instead of the dependency property, If I assign a view model directly to the Control it works fine. But in my condition, it is mandatory that I have properties like DataSource,SelectedIndex, etc on the user control for the end-user to use. Can anyone point out what is going wrong here?
Here, I have attached a copy of my complete code.
I downloaded your sample code, the problem should be in the binding.
<ItemsControl ItemsSource="{Binding Items}">
This way of writing is not recommended. In the ObservableCollection, Items is a protected property and is not suitable as a binding property.
You can try to bind dependency property directly in ItemsControl:
<ItemsControl ItemsSource="{x:Bind DropDownDataSource,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<CheckBox IsChecked="{x:Bind IsChecked, Mode=TwoWay}"
IsThreeState="False" >
<TextBlock Text="{x:Bind Text}" Foreground="Black" FontSize="24"/>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In addition, you may have noticed that I modified the style of CheckBox and rewritten the content to TextBlock, because in the default style of CheckBox, Foreground is not bound to the internal ContentPresenter.
Thanks.

Textbox in listbox not updating when lostfocus

I have a textbox inside of a listbox that I would like to update the ObservableCollection when the textbox loses focus. I tried using my collections CollectionChanged event as described in this post here to try to solve the problem. Right now the only way to update the collection is if I add or remove an item from the listbox. Am I going about this the wrong way? What am I missing for the textbox to update the collection?
MainWindow.xaml
<ListBox ItemsSource="{Binding DataLogList,Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding DataLogLabel}" Margin="5"/>
<TextBox Text="{Binding DataLogName,Mode=TwoWay,UpdateSourceTrigger=LostFocus}" Margin="5" Width="150"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MainViewModel.cs
public MainViewModel()
{
DataLogList = new ObservableCollection<DataLogContent>();
DataLogList.CollectionChanged += (s, e) =>
{
if (e.NewItems != null)
{
foreach (DataLogContent item in e.NewItems)
{
item.PropertyChanged += item_PropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (DataLogContent item in e.OldItems)
{
item.PropertyChanged -= item_PropertyChanged;
}
}
};
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged();
}
DataLogContent.cs
public class DataLogContent:ViewModelBase
{
private string dataLogLabel;
public string DataLogLabel
{
get { return this.dataLogLabel; }
set
{
this.dataLogLabel = value;
NotifyPropertyChanged();
}
}
private string dataLogName;
public string DataLogName
{
get { return this.dataLogName; }
set
{
this.dataLogLabel = value;
NotifyPropertyChanged();
}
}
}
I have it working based on this. My guess is that you may be over complicating the adding/removing logic of an item inside the ObservableCollection. There's no need to monitor the property changed event, as each object will already raise the event whenever a property within it changes.
Here's what I have:
namespace WpfApplication1
{
public class MainViewModel : ViewModelBase
{
public ObservableCollection<DataLogContent> DataLogList { get; private set; }
public MainViewModel()
{
DataLogList = new ObservableCollection<DataLogContent>();
DataLogList.Add(new DataLogContent
{
DataLogLabel = "Label",
DataLogName = "Name"
});
DataLogList.Add(new DataLogContent
{
DataLogLabel = "Label2",
DataLogName = "Name2"
});
}
}
public class DataLogContent : ViewModelBase
{
private string dataLogLabel;
public string DataLogLabel
{
get { return this.dataLogLabel; }
set
{
this.dataLogLabel = value;
OnPropertyChanged("DataLogLabel");
}
}
private string dataLogName;
public string DataLogName
{
get { return this.dataLogName; }
set
{
this.dataLogName = value;
OnPropertyChanged("DataLogName");
}
}
}
}
Simple ViewModelBase:
namespace WpfApplication1
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
}
}
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding DataLogList,Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding DataLogLabel}" Margin="5"/>
<TextBox Text="{Binding DataLogName,Mode=TwoWay,UpdateSourceTrigger=LostFocus}" Margin="5" Width="150"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>

Wpf MVVM - Tabbed interface is not working as expected

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;
}

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.

Two-way binding on a ContentControl

I'm creating a Key/Value pair editor and would like for the value to have a custom template based on the data type.
<TextBox x:Uid="txtKey" x:Name="txtKey" Grid.Column="1" Grid.Row="0" Text="{Binding ElementName=This, Path=KeyValuePair.Key}" VerticalAlignment="Top"/>
<ContentControl Grid.Column="1" Grid.Row="1"
x:Uid="ContentControl1" x:Name="ContentControl1"
Content="{Binding ElementName=This, Path=KeyValuePair.Value}"
LostFocus="ContentControl1_LostFocus"
DataContextChanged="ContentControl1_DataContextChanged" />
The codebehind for the host class looks like this:
public partial class KeyValueControl : ControlBase
{
private System.Collections.DictionaryEntry _dictionaryEntry;
private KeyValuePairObjectObject _KeyValuePair = new KeyValuePairObjectObject();
private DataTemplate _editorDataTemplate;
private Caelum.Libraries.Ui.Editors.Resources resources = new Editors.Resources();
public DataTemplate EditorDataTemplate
{
get { return _editorDataTemplate; }
set { _editorDataTemplate = value; SendPropertyChanged("EditorDataTemplate"); }
}
public KeyValuePairObjectObject KeyValuePair
{
get { return _KeyValuePair; }
set { _KeyValuePair = value; SendPropertyChanged("KeyValuePair"); }
}
public KeyValueControl()
{
InitializeComponent();
this.DataUpdated += new DataUpdatedHander(KeyValueControl_DataUpdated);
DataContextChanged += new DependencyPropertyChangedEventHandler(KeyValueControl_DataContextChanged);
}
void KeyValueControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
}
public override void Save()
{
base.Save();
}
void KeyValueControl_DataUpdated(object sender, object data)
{
if (Data != null)
{
_dictionaryEntry = (System.Collections.DictionaryEntry)Data;
KeyValuePair.Key = _dictionaryEntry.Key;
KeyValuePair.Value = _dictionaryEntry.Value;
if (KeyValuePair.Value != null)
{
EditorDataTemplate = resources.GetDataTemplate(_dictionaryEntry.Value.GetType());
ContentControl1.ContentTemplate = EditorDataTemplate;
}
}
}
}
DataTemplates are chosen via the resources class:
public DataTemplate GetDataTemplate(Type type)
{
if (type == typeof(string))
{
return TextInlineEditorTemplate;
}
if (type == typeof(bool))
{
return BooleanInlineEditorTemplate;
}
return null;
}
The DataTemplate that is displayed for a string is:
<DataTemplate x:Uid="TextInlineEditorTemplate" x:Key="TextInlineEditorTemplate" >
<Grid>
<TextBox x:Uid="txtTextIET1" x:Name="txtTextIET1" Width="300" Text="{Binding Path=DataContext, Mode=TwoWay, RelativeSource={RelativeSource Self}, BindsDirectlyToSource=True, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
The data binds OK to the key TextBox (txtKey) and DataTemplate TextBox (txtTextIET1), but changing the value on txtTextIET1 will not trigger the setter on the KeyValuePair property. I've not been able to find any examples of this scenario, so any help would be appreciated.
Didn't this work for you
<DataTemplate x:Uid="TextInlineEditorTemplate" x:Key="TextInlineEditorTemplate" >
<Grid>
<TextBox x:Uid="txtTextIET1" x:Name="txtTextIET1" Width="300" Text="{Binding}" />
</Grid>
</DataTemplate>

Categories

Resources