I used Blend for VS 2012 to create a template of a WP7.8 application. There were some sample data (located in a separate xaml file, see lower) which were shown in a design mode on the silverlight page correctly. Changing this data (with namespaces and class names) lead to editor errors and new data is not displayed in the design mode anymore. (the List component is shown as empty)
the main xaml file of the page is the following
<phone:PhoneApplicationPage
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
x:Class="DesignSketch.MainPage"
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Pivot Control-->
<controls:Pivot Title="Application">
<!--Pivot item one-->
<controls:PivotItem Header="List">
<!--Double line list with text wrapping-->
<ListBox x:Name="FirstListBox" ItemsSource="{Binding Items}" Margin="0,0,-12,0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432" Height="78">
<TextBlock Text="{Binding Sum}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PivotItem>
</controls:Pivot>
</Grid>
</phone:PhoneApplicationPage>
as can be seen it uses
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
for design mode binding. Howerever the error (hint) is shown "Errors found in MainViewModelSampleData.xaml"
the contents of this xaml is
<viewModels2:ExpensesPageVM
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels2="clr-namespace:DesignSketch"
>
<viewModels2:ExpensesPageVM.Items>
<viewModels2:Expense Sum="10" Description="Fee1" />
<viewModels2:Expense Sum="600" Description="Fee2" />
</viewModels2:ExpensesPageVM.Items>
</viewModels2:ExpensesPageVM>
for it the following hint-errors are shown
The attachable property Items was not found in type ExpensesPageVM
The type viewModels2:Expense was not found. Verify that you are not missing an assembly reference and that all referenced assemblies have been built
The classes ExpensesPageVM and Expense are both located in DesignSketch namespace and have the following code:
using System;
using System.Linq;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace DesignSketch
{
public class ExpensesPageVM : INotifyPropertyChanged
{
public ExpensesPageVM()
{
this.Items = new ObservableCollection<Expense>();
}
/// <summary>
/// A collection for ItemViewModel objects.
/// </summary>
public ObservableCollection<Expense> Items { get; set; }
private string _sampleProperty = "Sample Runtime Property Value";
/// <summary>
/// Sample ViewModel property; this property is used in the view to display its value using a Binding
/// </summary>
/// <returns></returns>
public string SampleProperty
{
get
{
return _sampleProperty;
}
set
{
_sampleProperty = value;
NotifyPropertyChanged("SampleProperty");
}
}
public bool IsDataLoaded
{
get;
private set;
}
/// <summary>
/// load initial data from sdf
/// </summary>
public void LoadData()
{
//...
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
other file
using System;
using System.Data.Linq.Mapping;
using System.ComponentModel;
namespace DesignSketch
{
[Table]
public class Expense: INotifyPropertyChanged, INotifyPropertyChanging
{
private int expenseID;
[Column(IsPrimaryKey = true,
IsDbGenerated = true,
DbType="INT NOT NULL Identity",
CanBeNull=false,
AutoSync=AutoSync.OnInsert)]
public int ExpenseID { get {return expenseID;}
set
{
if (expenseID == value) return;
NotifyPropertyChanging("ExpenseID");
expenseID = value;
NotifyPropertyChanged("ExpenseID");
}
}
private string description { get; set; }
[Column]
public string Description { get { return description; }
set
{
if (description == value) return;
NotifyPropertyChanging("Description");
description = value;
NotifyPropertyChanged("Description");
}
}
private decimal sum;
[Column]
public decimal Sum
{
get { return sum; }
set
{
if (sum == value) return;
NotifyPropertyChanging("Sum");
sum = value;
NotifyPropertyChanged("Sum");
}
}
private DateTime date;
[Column]
public DateTime Date { get { return date; }
set
{
if (date == value) return;
NotifyPropertyChanging("Date");
date = value;
NotifyPropertyChanged("Date");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangingEventHandler PropertyChanging;
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
}
}
Expense is customized to be stored in a local db of the phone.
I have practically the same code working (generated by Blend) where the sample data is shown in a design time perfectly. Does anybody have a clue why it isn't with this code ?
If DesignSketch is not in the current assembly, then change this line
xmlns:viewModels2="clr-namespace:DesignSketch"
to
xmlns:viewModels2="clr-namespace:DesignSketch;assembly=NameOfAssemblyThatHoldsDesignSketch"
Related
How to store the content of TextBox in Model layer to be in line with MVVM?
I've made simple demo application to practice MVVM. It consists of main TextBox and 2 additional TextBoxes just for test if the app works properly.
In ViewModel I have TextContent class which implements INotifyPropertyChanged and it has Text property and the Text of MainTextBox is bindded to this and it works correctly.
In Model I have TextStore property which I try to update in the setter of Text property from ViewModel.TextContent, using simple method ModelUpdate().
And this model updating doesn't work.
Could you tell me ho can I transfer the content of TextBox which is stored in ViewModel property to the Model layer? And being in line in MVVM pattern?
Here the code:
View: (Here, the third TextBox is bindded to the model - I know, this is not compatible with MVVM idea but this is just for check the value of TextStore property from Model layer)
<Window x:Class="MVVM_TBDB_2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVM_TBDB_2"
xmlns:vm="clr-namespace:MVVM_TBDB_2.ViewModel"
xmlns:m="clr-namespace:MVVM_TBDB_2.Model"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<m:TextContent x:Key="ModelTextContent" />
</Window.Resources>
<Window.DataContext>
<vm:TextContent />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Name="MainTB" Grid.Row="0" Margin="10" AcceptsReturn="True"
Text="{Binding Text, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
<Button Name="SaveButton" Content="Save" Grid.Row="1" Margin="10,2" Padding="20,0" HorizontalAlignment="Left" />
<TextBox Name="ControlTB" Grid.Row="1" Margin="30,2,2,2" Width="100" Text="{Binding Text, Mode=OneWay}" />
<TextBox Name="ControlTB2" Grid.Row="1" Margin="300,2,2,2" Width="100" DataContext="{StaticResource ModelTextContent}"
Text="{Binding TextStock, Mode=TwoWay}" />
</Grid>
</Window>
ViewModel:
class TextContent : INotifyPropertyChanged
{
private Model.TextContent model;
public TextContent()
{
model = new Model.TextContent();
}
private string _Text;
public string Text
{
get { return _Text; }
set
{
_Text = value;
OnPropertyChanged("Text");
ModelUpdate(_Text);
}
}
private void OnPropertyChanged(string parameter)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(parameter));
}
public event PropertyChangedEventHandler PropertyChanged;
private void ModelUpdate(string textToUpdate)
{
model.TextStock = textToUpdate;
}
}
Model:
class TextContent
{
private string _TextStock;
public string TextStock
{
get { return _TextStock; }
set { _TextStock = value; }
}
}
See Here I have implemented your requirement.
Attach the data context from code behind.
Implement the INotifyPropertyChanged interface in Model.
Make the TextStock property as binded property.
MainWindow.cs
public TextContent _model { get; set; }
public TextContentViewModel _viewModel { get; set; }
public MainWindow()
{
InitializeComponent();
_viewModel = new TextContentViewModel();
_model = new TextContent();
this.DataContext = _viewModel;
ControlTB2.DataContext = _model;
}
Your ViewModel Class
private TextContent model;
public TextContentViewModel()
{
}
private string _Text;
public string Text
{
get { return _Text; }
set
{
_Text = value;
OnPropertyChanged("Text");
if (model != null)
{
ModelUpdate(_Text);
}
else
{
model = ((Application.Current.MainWindow as MainWindow).ControlTB2).DataContext as TextContent;
}
}
}
private void OnPropertyChanged(string parameter)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(parameter));
}
public event PropertyChangedEventHandler PropertyChanged;
private void ModelUpdate(string textToUpdate)
{
model.TextStock = textToUpdate;
}
}
Model Class
private string _TextStock;
public string TextStock
{
get { return _TextStock; }
set { _TextStock = value; OnPropertyChanged("TextStock"); }
}
private void OnPropertyChanged(string parameter)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(parameter));
}
public event PropertyChangedEventHandler PropertyChanged;
Note: I have renamed the class names as per my convenience.
I have an ItemsControl with ItemsSource bound to an IEnumerable<MyDataItem>.
The ItemTemplate consists of two textboxes. (Friendly name & name). This is how it looks like: http://i.stack.imgur.com/Rg1dC.png
As soon as the "Friendly name" field is filled in, I add an empty row. I use the LostKeyboardFocus event to check whether to add an empty "MyDataItem" and refresh the IEnumerable<MyDataItem> property.
The problem: I loose the focus when adding an item. So if I tab from friendly name to name and a new row is added, the focus is lost from name. How can I solve this?
EDIT: Underneath some code to show my problem. I want to be able to TAB from cell to cell. When both cells of a row are left empty, I want to remove that line. At the end I want to have an empty row (both cells empty). At this point the code works, but loses focus if you use tab. And working with a listbox makes that TAB doesn't work to go to the next item in list.
XAML:
<Window x:Class="Focus.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Focus"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=True}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="DataTemplate1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="5"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox LostKeyboardFocus="TextBox_LostKeyboardFocus">
<TextBox.Text>
<Binding Path="FriendlyName" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<TextBox Grid.Column="2" LostKeyboardFocus="TextBox_LostKeyboardFocus">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Margin="10" ItemsSource="{Binding OrderedItems}" ItemTemplate="{DynamicResource DataTemplate1}" HorizontalContentAlignment="Stretch">
</ListBox>
</Grid>
Code behind:
using System.Windows;
using System.Windows.Input;
namespace Focus
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
private void TextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
MainViewModel vm = this.DataContext as MainViewModel;
vm.CheckToAddEmptyItem();
}
}
}
The ViewModel
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Focus
{
public class MainViewModel : INotifyPropertyChanged
{
private List<MyItem> _myItems = new List<MyItem>();
public IEnumerable<MyItem> OrderedItems
{
get { return _myItems.OrderBy(i => i.IsEmpty); }
}
internal void CheckToAddEmptyItem()
{
int count = _myItems.Count(i => i.IsEmpty);
if (count == 0)
{
_myItems.Add(new MyItem());
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs("OrderedItems"));
}
else if (count > 1)
{
var items = _myItems.Where(i => i.IsEmpty).Skip(1).ToArray();
foreach (MyItem item in items)
_myItems.Remove(item);
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs("OrderedItems"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
for(int i=1; i <= 5; ++i)
{
_myItems.Add(new MyItem() { FriendlyName = "Item #" + i, Name = "ITEM" + i });
}
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs("OrderedItems"));
CheckToAddEmptyItem();
}
}
}
The MyItem class:
using System.ComponentModel;
namespace Focus
{
public class MyItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name = string.Empty;
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
PropertyChanged(this, new PropertyChangedEventArgs("IsEmpty"));
}
}
}
}
private string _friendlyName = string.Empty;
public string FriendlyName
{
get { return _friendlyName; }
set
{
if (value != _friendlyName)
{
_friendlyName = value;
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("FriendlyName"));
PropertyChanged(this, new PropertyChangedEventArgs("IsEmpty"));
}
}
}
}
public bool IsEmpty
{
get { return string.IsNullOrEmpty(Name) && string.IsNullOrEmpty(FriendlyName); }
}
}
}
ItemsControl is not that friendly according to your needs as there are no properties to get a perticular selected item. Try using a Listbox Instead, as it exposes the properties like SelectedItem, SelectedIndex etc, Using these properties you can get the child control at any index value.
PS: I could elaborate my answer if you are looking to use alistbox and get the child elements.
I'm using the following (simplified) code to display an element in all the items in an ItemsControl except the first:
<TheElement Visibility="{Binding RelativeSource={RelativeSource PreviousData},
Converter={StaticResource NullToVisibility}}/>
NullToVisibility is a simple converter that returns Visibility.Hidden if the source is null, Visibility.Visible otherwise.
Now, this works fine when binding the view initially, or adding elements to the list (an ObservableCollection), but the element is not made invisible on the second element when removing the first.
Any ideas on how to fix this?
Had some wasted code leftover from a previous answer... might as well use it here:
The key is to refresh the viewsource e.g. :
CollectionViewSource.GetDefaultView(this.Categories).Refresh();
Full example source below. Remove First Item removes first element and refreshes the view:
RelativeSourceTest.xaml
<UserControl x:Class="WpfApplication1.RelativeSourceTest"
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:PersonTests="clr-namespace:WpfApplication1" mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<PersonTests:NullToVisibilityConvertor x:Key="NullToVisibility"/>
</UserControl.Resources>
<Grid>
<StackPanel Background="White">
<Button Content="Remove First Item" Click="Button_Click"/>
<ListBox ItemsSource="{Binding Categories}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Checked, Mode=TwoWay}" >
<StackPanel>
<TextBlock Text="{Binding CategoryName}"/>
<TextBlock Text="Not The First"
Visibility="{Binding RelativeSource={RelativeSource PreviousData},
Converter={StaticResource NullToVisibility}}"/>
</StackPanel>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</UserControl>
RelativeSourceTest.xaml.cs
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApplication1
{
public partial class RelativeSourceTest : UserControl
{
public ObservableCollection<Category> Categories { get; set; }
public RelativeSourceTest()
{
InitializeComponent();
this.Categories = new ObservableCollection<Category>()
{
new Category() {CategoryName = "Category 1"},
new Category() {CategoryName = "Category 2"},
new Category() {CategoryName = "Category 3"},
new Category() {CategoryName = "Category 4"}
};
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Categories.RemoveAt(0);
CollectionViewSource.GetDefaultView(this.Categories).Refresh();
}
}
}
Category.cs
using System.ComponentModel;
namespace WpfApplication1
{
public class Category : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _checked;
public bool Checked
{
get { return _checked; }
set
{
if (_checked != value)
{
_checked = value;
SendPropertyChanged("Checked");
}
}
}
private string _categoryName;
public string CategoryName
{
get { return _categoryName; }
set
{
if (_categoryName != value)
{
_categoryName = value;
SendPropertyChanged("CategoryName");
}
}
}
public virtual void SendPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
It's '17 now, but the problem is here. MVVM approach (as I see it):
public class PreviousDataRefreshBehavior : Behavior<ItemsControl> {
protected override void OnAttached() {
var col = (INotifyCollectionChanged)AssociatedObject.Items;
col.CollectionChanged += OnCollectionChanged;
}
protected override void OnDetaching() {
var col = (INotifyCollectionChanged)AssociatedObject.Items;
col.CollectionChanged -= OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
if(e.Action != NotifyCollectionChangedAction.Remove) {
return;
}
CollectionViewSource.GetDefaultView(AssociatedObject.ItemsSource).Refresh();
}
}
and usage:
<ItemsControl>
<int:Interaction.Behaviors>
<behaviors:PreviousDataRefreshBehavior/>
</int:Interaction.Behaviors>
</ItemsControl>
Underlying CollectionViewSource has to be refreshed after remove.
CollectionViewSource.GetDefaultView(this.Items).Refresh();
I've got a datatemplate for a tabcontrol's itemtemplate as follows;
<DataTemplate x:Key="TabItemTemplate">
<DockPanel Width="120">
<Button
Command="{Binding Path=DataContext.DeleteTimeTableCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
Margin="0,1,0,0"
Padding="0"
VerticalContentAlignment="Bottom"
Width="16" Height="16" />
This is OK as it gives me a button in the tabcontrol to allow for deleting the current tabitem.
Trouble I'm having is that the Delete command I'm binding to has a canExecute method which updates all buttons across all of the tabs in the tabcontrol. I just want the current tab to be affected.
I've got property CanDelete which I want to include in my Command. I'm trying to find a good example on CommandParameters as I think this is the way I need to go.
Has anyone got a good suggestion for the best way to do this?
Thanks.
I doubt that you still need help with this, but figured I'd take a crack at answering it anyway.
The way that I have done it in the past is to make the collection of items that you are binding to your TabControl be a collection of simple ViewModel objects. That way you can implement the CanXXX logic for each one of the tabs instead of the TabControl or view as a whole.
In this example, I am using the RelayCommand class that is shown in the Josh Smith's MVVM article.
MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
namespace TabBinding.ViewModels
{
class MainViewModel : ViewModelBase
{
private ObservableCollection<TabViewModel> _Tabs;
public ObservableCollection<TabViewModel> Tabs
{
get { return _Tabs; }
set
{
_Tabs = value;
OnPropertyChanged(this, "Tabs");
}
}
public MainViewModel()
{
var tabs = new ObservableCollection<TabViewModel>();
tabs.Add(new TabViewModel() { TabHeader = "Tab1", Content="Content For Tab1" });
tabs.Add(new TabViewModel() { TabHeader = "Tab2", Content = "Content For Tab2" });
tabs.Add(new TabViewModel() { TabHeader = "Tab3", Content = "Content For Tab3" });
tabs.Add(new TabViewModel() { TabHeader = "Tab4", Content = "Content For Tab4" });
Tabs = tabs;
}
}
}
TabViewModel.cs
using System.Windows.Input;
using System.Windows;
namespace TabBinding.ViewModels
{
class TabViewModel : ViewModelBase
{
RelayCommand _CloseTabCommand;
private string _TabHeader;
public string TabHeader
{
get { return _TabHeader; }
set
{
_TabHeader = value;
OnPropertyChanged(this, "TabHeader");
}
}
private string _Content;
public string Content
{
get { return _Content; }
set
{
_Content = value;
OnPropertyChanged(this, "Content");
}
}
public ICommand CloseTabCommand
{
get
{
if (_CloseTabCommand == null)
{
_CloseTabCommand = new RelayCommand(
param => this.CloseTab(),
param => this.CanCloseTab
);
}
return _CloseTabCommand;
}
}
public void CloseTab()
{
MessageBox.Show("Close Me!");
}
bool CanCloseTab
{
get { return (TabHeader == "Tab2" || TabHeader == "Tab4"); }
}
}
}
ViewModelBase.cs
using System.ComponentModel;
namespace TabBinding.ViewModels
{
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
}
}
RelayCommand.cs
using System;
using System.Diagnostics;
using System.Windows.Input;
namespace TabBinding
{
/// <summary>
/// A command whose sole purpose is to
/// relay its functionality to other
/// objects by invoking delegates. The
/// default return value for the CanExecute
/// method is 'true'.
/// </summary>
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
}
MainWindow.xaml
<Window x:Class="TabBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TabBinding.ViewModels"
Title="MainWindow" Height="360" Width="550">
<Window.Resources>
<vm:MainViewModel x:Key="Data" />
</Window.Resources>
<Grid DataContext="{StaticResource Data}">
<TabControl
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10,10,10,10"
Width="500"
Height="300"
ItemsSource="{Binding Tabs}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="X" Margin="0,0,10,0" Command="{Binding CloseTabCommand}" />
<TextBlock Text="{Binding TabHeader}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Content" Value="{Binding Content}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
App.xaml
<Application x:Class="TabBinding.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
If anyone is still interested about the answer, you can use the CommandParameter binding extension to pass the current model.
<Button Command="{Binding Path=DataContext.DeleteTimeTableCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" CommandParameter="{Binding}" />
The passed object is going to be the DataContext of the tab item. The solution requires the ICommand implementation to handle the given parameter properly (casting etc). Furthermore, the RequerySuggested event should be raised after any modification on the model, since WPF cannot figure out when to requery the CanExecute methods on the tabs. Another thing to keep in mind when using asynchron programming models is to raise the refresh event from the UI thread only. Nothing is going to happen otherwise.
Is there any way, how to force ObservableCollection to fire CollectionChanged?
I have a ObservableCollection of objects ListBox item source, so every time I add/remove item to collection, ListBox changes accordingly, but when I change properties of some objects in collection, ListBox still renders the old values.
Even if I do modify some properties and then add/remove object to the collection, nothing happens, I still see old values.
Is there any other way around to do this? I found interface INotifyPropertyChanged, but I don't know how to use it.
I agree with Matt's comments above. Here's a small piece of code to show how to implement the INotifyPropertyChanged.
===========
Code-behind
===========
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
Nicknames names;
public Window1()
{
InitializeComponent();
this.addButton.Click += addButton_Click;
this.names = new Nicknames();
dockPanel.DataContext = this.names;
}
void addButton_Click(object sender, RoutedEventArgs e)
{
this.names.Add(new Nickname(myName.Text, myNick.Text));
}
}
public class Nicknames : System.Collections.ObjectModel.ObservableCollection<Nickname> { }
public class Nickname : System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
void Notify(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propName));
}
}
string name;
public string Name
{
get { return name; }
set
{
name = value;
Notify("Name");
}
}
string nick;
public string Nick
{
get { return nick; }
set
{
nick = value;
Notify("Nick");
}
}
public Nickname() : this("name", "nick") { }
public Nickname(string name, string nick)
{
this.name = name;
this.nick = nick;
}
public override string ToString()
{
return Name.ToString() + " " + Nick.ToString();
}
}
}
XAML
<Window x:Class="WpfApplication1.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>
<DockPanel x:Name="dockPanel">
<TextBlock DockPanel.Dock="Top">
<TextBlock VerticalAlignment="Center">Name: </TextBlock>
<TextBox Text="{Binding Path=Name}" Name="myName" />
<TextBlock VerticalAlignment="Center">Nick: </TextBlock>
<TextBox Text="{Binding Path=Nick}" Name="myNick" />
</TextBlock>
<Button DockPanel.Dock="Bottom" x:Name="addButton">Add</Button>
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
</DockPanel>
</Grid>
Modifying properties on the items in your collection won't fire NotifyCollectionChanged on the collection itself - it hasn't changed the collection, after all.
You're on the right track with INotifyPropertyChanged. You'll need to implement that interface on the class that your list contains. So if your collection is ObservableCollection<Foo>, make sure your Foo class implements INotifyPropertyChanged.