I have a simple app here that uses an observable collection and generates tabcontrol tabs per item. However when I try to add additional tabs it fails. I also noticed when i attempt to print the data in the collection, it appears i can not do that. I get the following error for both lines of code.
Error 1 An object reference is required for the non-static field,
method, or property
InitializeComponent();
//ERRORS on both lines below
Console.WriteLine(ViewModel.TabItems);
ViewModel.AddContentItem();
MainWindow.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//ERRORS on both lines below
Console.WriteLine(ViewModel.TabItems);
ViewModel.AddContentItem();
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="250">
<Window.DataContext>
<data:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TabControl ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
ViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication1
{
public class ViewModel : ObservableObject
{
private ObservableCollection<TabItem> tabItems;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ViewModel()
{
TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
public void AddContentItem()
{
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
}
public class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
}
}
ObservableObject.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
You should do this instead
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var viewModel = (ViewModel) this.DataContext;
Debug.WriteLine(viewModel.TabItems);
viewModel.AddContentItem();
}
}
}
Updated thanks King King
First, this is a simplified version from a wizard control using MVVM. The problem is just easier to reproduce as described below
After much narrowing down, I have resolved an infinite exception in my code to be due to the WPF ContentControl. However, I have yet to figure out how to handle it, other than try-catch wrapping all of my possible instantiation code. Here is sample code that reproduces this...any help on how to keep this infinite exception from occurring would be greatly appreciated.
Additional Details
To sum up, the problem is that if the content control changes its contents, and the thing being loaded in throws an exception, then it will throw, then retry the load, causing the throw again and again.
MainWindow.xaml
<Window x:Class="WpfApplication8.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" Name ="Main">
<Grid>
<ContentControl Name="bar" Content="{Binding ElementName=Main, Path=foo}"/>
<Button Click="ButtonBase_OnClick" Margin="20" Width="50"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private UserControl _foo;
public UserControl foo
{
get { return _foo; }
set { _foo = value; OnPropertyChanged("foo"); }
}
public MainWindow()
{
InitializeComponent();
foo = new UserControl1();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
foo = new UserControl2();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
UserControl1 is blank and all default
UserControl2.xaml.cs
public UserControl2()
{
InitializeComponent();
throw new Exception();
}
Do not bind ContentControl to MainWindow. Instead use DataTemplates to select the content for the MainWindow. One example-contrived way of doing it is to bind the ContentControl's Content to the DataContext of the MainWindow.
First some observable test data is needed. The specifics of this data are not important. The main point is to have two different classes of test data from which to choose - TestData.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace fwWpfDataTemplate
{
// Classes to fill TestData
public abstract class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Student : Person { }
public class Employee : Person
{
float _salary;
public float Salary
{
get { return _salary; }
set
{
_salary = value;
OnPropertyChanged("Salary");
}
}
}
public class TestData : ObservableCollection<Person>
{
public TestData()
: base(new List<Person>()
{
new Student { Name = "Arnold" },
new Employee { Name = "Don", Salary = 100000.0f }
}) { }
}
}
Then add DataTemplates to MainWindow's resources - MainWindow.xaml:
<Window x:Class="fwWpfDataTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:fwWpfDataTemplate"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type me:Student}">
<StackPanel>
<TextBlock Text="Student"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type me:Employee}">
<StackPanel>
<TextBlock Text="Employee"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="Salary"/>
<TextBlock Text="{Binding Salary}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Change Data Context" Click="Button_Click" />
<ContentControl Grid.Row="1" Content="{Binding}"/>
</Grid>
</Window>
Note: instead of the StackPanels the contents of the DataTemplates could be UserControl1, UserControl2, etc.
Then add some code to change the data context - MainWindow.cs:
using System.Windows;
namespace fwWpfDataTemplate
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
TestData testData = new TestData();
int testIndex = -1;
private void Button_Click(object sender, RoutedEventArgs e)
{
testIndex = (testIndex + 1) % testData.Count;
this.DataContext = testData[testIndex];
}
}
}
Enjoy.
I'm starting to study about WFM MVVM pattern.
But I can't understand why this Button click works without binding any event or action.
View
<Window x:Class="WPF2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WPF2.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:MainWindowViewModel x:Key="MainViewModel" />
</Window.Resources>
<Grid x:Name="LayoutRoot"
DataContext="{Binding Source={StaticResource MainViewModel}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,7,0,0" Name="txtID" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId}" />
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,35,0,0" Name="txtName" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.Name}" />
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,61,0,0" Name="txtPrice" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.Price}" />
<Label Content="ID" Grid.Row="1" HorizontalAlignment="Left" Margin="12,12,0,274" Name="label1" />
<Label Content="Price" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="12,59,0,0" Name="label2" VerticalAlignment="Top" />
<Label Content="Name" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="12,35,0,0" Name="label3" VerticalAlignment="Top" />
<Button Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="141" Height="23" Margin="310,40,0,0" Content="Update" />
<ListView Name="ListViewProducts" Grid.Row="1" Margin="4,109,12,23" ItemsSource="{Binding Path=Products}" >
<ListView.View>
<GridView x:Name="grdTest">
<GridViewColumn Header="Product ID" DisplayMemberBinding="{Binding ProductId}" Width="100"/>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="250" />
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price}" Width="127" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ViewModel
class MainWindowViewModel : INotifyPropertyChanged
{
public const string ProductsPropertyName = "Products";
private ObservableCollection<Product> _products;
public ObservableCollection<Product> Products
{
get
{
return _products;
}
set
{
if (_products == value)
{
return;
}
_products = value;
RaisePropertyChanged(ProductsPropertyName);
}
}
public MainWindowViewModel()
{
_products = new ObservableCollection<Product>
{
new Product {ProductId=1, Name="Pro1", Price=11},
new Product {ProductId=2, Name="Pro2", Price=12},
new Product {ProductId=3, Name="Pro3", Price=13},
new Product {ProductId=4, Name="Pro4", Price=14},
new Product {ProductId=5, Name="Pro5", Price=15}
};
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
I found this code from a tutorial i've read. When i click on a row from listview, the textbox values are set. And when I edit those values and click a button, the values on listview are updated.
There is no event nor command bound on the button. So how do button click update the values from listview??
I know this is old but I was looking into this scenario and found my answer.
First, in MVVM theory the "code-behind" shouldn't have code all the code should be in the ViewModel class.
So in order to implement button click, you have this code in the ViewModel (besides the INotifyPropertyChanged implementation):
public ICommand ButtonCommand { get; set; }
public MainWindowViewModel()
{
ButtonCommand = new RelayCommand(o => MainButtonClick("MainButton"));
}
private void MainButtonClick(object sender)
{
MessageBox.Show(sender.ToString());
}
Using the class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace <your namespace>.ViewModels
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
if (execute == null) throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter ?? "<N/A>");
}
}
}
And in XAML:
<Window.DataContext>
<viewModels:MainWindowViewModel />
</Window.DataContext> <Button Content="Button" Command="{Binding ButtonCommand}" />
In above sample snippet, ListView is binded to an ObservableCollection of Product which is internally implemented INotfiyPropertyChanged Interface. This interface is responsible for raising PropertyChanged event and updates binded UI element values whenever it change.
More over you can see here, the Text property of all TextBoxes are binded as Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId}"
Binding ElementName - This is a markup extension which will tell XAML compiler to bind your ListView to Textbox control
Path - This will point to a specific property of Binded UI Element. In this case it will point to ListView.SelectedItem object property. ie. Product.ProductID
The binding mode of WPF UI element is TwoWay by default. So it will update both Source and Target whenever the value changes. You can try this by changing mode to OneWay
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,7,0,0" Name="txtID" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId,Mode=OneWay}" />
You need to set UpdateSourceTrigger to Explicit and on the Click event of the Button you need to update the binding source.
The Buttons Click event is executed first before Command. Hence the source properties can be updated in the Click event. I have modified your code to demonstrate this.
Note:
I have written the view model in code behind for simplicity.
I have used the RelayCommand from the MVVMLight library.
1st XAML Change
<Button Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="141" Height="23" Margin="310,40,0,0" Content="Update" Click="Button_Click" Command="{Binding UpdateCommand}"/>
2nd XAML Change
<ListView Name="ListViewProducts" Grid.Row="1" Margin="4,109,12,23" ItemsSource="{Binding Path=Products}" SelectedItem="{Binding SelectedProduct}" >
Code behind
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WPF2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
txtID.GetBindingExpression(TextBox.TextProperty).UpdateSource();
txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
txtPrice.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
public class MainWindowViewModel : INotifyPropertyChanged
{
public const string ProductsPropertyName = "Products";
private ObservableCollection<Product> _products;
public ObservableCollection<Product> Products
{
get
{
return _products;
}
set
{
if (_products == value)
{
return;
}
_products = value;
RaisePropertyChanged(ProductsPropertyName);
}
}
private Product _SelectedProduct;
public Product SelectedProduct
{
get { return _SelectedProduct; }
set
{
_SelectedProduct = value;
RaisePropertyChanged("SelectedProduct");
}
}
private ICommand _UpdateCommand;
public ICommand UpdateCommand
{
get
{
if (_UpdateCommand == null)
{
_UpdateCommand = new RelayCommand(() =>
{
MessageBox.Show( String.Format("From ViewModel:\n\n Updated Product : ID={0}, Name={1}, Price={2}", SelectedProduct.ProductId, SelectedProduct.Name, SelectedProduct.Price));
});
}
return _UpdateCommand;
}
}
public MainWindowViewModel()
{
_products = new ObservableCollection<Product>
{
new Product {ProductId=1, Name="Pro1", Price=11},
new Product {ProductId=2, Name="Pro2", Price=12},
new Product {ProductId=3, Name="Pro3", Price=13},
new Product {ProductId=4, Name="Pro4", Price=14},
new Product {ProductId=5, Name="Pro5", Price=15}
};
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
I am new to MVVM and still trying to get a grasp on it so let me know if I'm setting this up wrong. What I have is a UserControl with a ListView in it. I populate this ListView with data from the ViewModel then add the control to my MainView. On my MainView I have a button that I want to use to add an item to the ListView. Here is what I have:
Model
public class Item
{
public string Name { get; set; }
public Item(string name)
{
Name = name;
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private ObservableCollection<Item> _itemCollection;
public ViewModel()
{
ItemCollection = new ObservableCollection<Item>()
{
new Item("One"),
new Item("Two"),
new Item("Three"),
new Item("Four"),
new Item("Five"),
new Item("Six"),
new Item("Seven")
};
}
public ObservableCollection<Item> ItemCollection
{
get
{
return _itemCollection;
}
set
{
_itemCollection = value;
OnPropertyChanged("ItemCollection");
}
}
}
View (XAML)
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Orientation="Vertical">
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<Grid>
<ListView ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding ItemCollection}">
</ListView>
</Grid>
MainWindow
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.mainContentControl.Content = new ListControl();
}
private void Button_Add(object sender, RoutedEventArgs e)
{
}
}
MainWindow (XAML)
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Width="100" Height="30" Content="Add" Click="Button_Add" />
</StackPanel>
<ContentControl x:Name="mainContentControl" />
</DockPanel>
</Grid>
Now, from what I understand, I should be able to just an item to ItemCollection and it will be updated in the view. How do I do this from the Button_Add event?
Again, if I'm doing this all wrong let me know and point me in the right direction. Thanks
You should not interact directly with the controls.
What you need to do is define a Command (a class that implements the ICommand-interface) and define this command on your ViewModel.
Then you bind the Button's command property to this property of the ViewModel. In the ViewModel you can then execute the command and add an item directly to your list (and thus the listview will get updated through the automatic databinding).
This link should provide more information:
http://msdn.microsoft.com/en-us/library/gg405484(v=pandp.40).aspx#sec11
I just can't figure it out. What I am missing to bound the Textblock?
I need the TextBlock to update everytime I select a new item in the ListView.
This is a sample I made. I my real application, I used the id from the ListView1 to fetch something from my DB that I want to display in my textBlock..
I know WPF binds to Properties and I need to implement INotifyPropertyChanged but I can't get the bindings right or maybe I am missing something else?
I have added DateTime.Now.TosString() just to see more clearly if the TextBlock changes.
XAML:
<Window x:Class="WpfSO.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" x:Name="txtBlockPerson"
Text="{Binding MyPerson}" />
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView1"
ItemsSource="{Binding ListData}"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="ListView1_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
C#
using System;
using System.ComponentModel;
using System.Windows;
using System.Collections.ObjectModel;
namespace WpfSO
{
public partial class MainWindow : Window
{
private ObservableCollection<Person> ListData { get; set; }
private const string _myName = "You clicked on: ";
public Person MyPerson { get; set; }
public MainWindow()
{
InitializeComponent();
// TextBlock
MyPerson = new Person(_myName);
txtBlockPerson.DataContext = MyPerson;
// ListView
ListData = new ObservableCollection<Person>();
var p1 = new Person("p1");
var p2 = new Person("p2");
ListData.Add(p1);
ListData.Add(p2);
ListView1.ItemsSource = ListData;
}
private void ListView1_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
MyPerson.Name = _myName + ListView1.SelectedItem + ". Time: " +DateTime.Now.ToString();
}
}
public class Person : INotifyPropertyChanged
{
private string _name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("PersonName");
}
}
}
public Person(string name)
{
Name = name;
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
The OnPropertyChanged needs to have the correct name of your property..
Instead of OnPropertyChanged("PersonName"); use
OnPropertyChanged("Name");