I have a simple Label that should include the bound .Count value of a Property of an ObservableCollection.
The thing is, that the result is always 0 (zero). The same Property is bound to a DataGrid, which works perfectly and even updates if something has changed in the Collection.
What am I doing wrong here?
Here is my code:
<Label ContentStringFormat="Members: {0}">
<Label.Content>
<Binding Path="MembersList.Count" Mode="OneWay" UpdateSourceTrigger="Default" />
</Label.Content>
</Label>
The Property looks like:
public static ObservableCollection<Mitglied> MembersList { get; set; }
I can only assume you've not actually added any items to the collection. If you think you are, you'll have to give us a more complete repro.
This works perfectly for me:
MainWindow.xaml
<Window x:Class="SO18124125.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">
<StackPanel>
<Button x:Name="addButton">Add</Button>
<Label>
<Label.Content>
<Binding Path="Items.Count" Mode="OneWay" UpdateSourceTrigger="Default"/>
</Label.Content>
</Label>
</StackPanel>
</Window>
MainWindow.xaml.cs
namespace SO18124125
{
using System.Collections.ObjectModel;
using System.Windows;
public partial class MainWindow : Window
{
private static readonly ObservableCollection<string> items = new ObservableCollection<string>();
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.addButton.Click += delegate
{
items.Add("foo");
};
}
public static ObservableCollection<string> Items
{
get { return items; }
}
}
}
BTW, this is far more succinct:
<Label Content="{Binding Items.Count}" ContentStringFormat="Members: {0}"/>
You can try This...
MainWindow.Xaml.cs->
int Counter = 0;
private static ObservableCollection<string> _MemberList = new ObservableCollection<string>();
// Suppose it is of String type..I took it as of String type to check my case
public static ObservableCollection<string> MemberList
{
get { return MainWindow._MemberList; }
set { MainWindow._MemberList = value; }
}
MainWindow()
{
InitializeComponent();
MemberList.Add("0");
MemberList.Add("1");
MemberList.Add("2");
Label1.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
MemberList.RemoveAt(Counter);
Counter++;
}
catch(Exception ex)
{
string strTemp=ex.Message();
}
}
MainWindow.xaml->
<Grid>
<Label Name="Label1" ContentStringFormat="Members: {0}" Margin="0,56,141,38" RenderTransformOrigin="0.158,1.154" HorizontalAlignment="Right" Width="183">
<Label.Content>
<Binding Path="MemberList.Count" Mode="OneWay" UpdateSourceTrigger="Default"/>
</Label.Content>
</Label>
<Button Click="Button_Click" Width="100" Height="20" Content="click" Margin="43,169,360,122" />
</Grid>
Hi You will have to Notify on CollectionChanged then it will Work
public class ViewModel: INotifyPropertyChanged
{
public ObservableCollection<string> MembersList { get; set; }
public ViewModel()
{
MembersList = new ObservableCollection<string>();
MembersList.CollectionChanged += collection_CollectionChanged;
MembersList.Add("wfwef");
}
void collection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Notify("MembersList.Count");
}
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
DataGrid uses this collectionChanged and hence working for DataGrid.
Related
I have model Partner:
public class Partner
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Comment { get; set; }
public override string ToString()
{
return Title;
}
}
view with this xaml:
<Window x:Class="WpfExtandedTextBox.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:ModelViews="clr-namespace:WpfExtandedTextBox"
d:DataContext="{d:DesignInstance ModelViews:ViewModel}"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
<Window.Resources>
<ControlTemplate x:Key="entityTextBoxTemplate" TargetType="TextBox">
<StackPanel Orientation="Horizontal">
<TextBox MinWidth="200" Text="{Binding Partner.Title, Mode=TwoWay}"/>
<TextBlock MaxWidth="0" Visibility="Hidden" Text="{Binding Partner.Id, Mode=TwoWay}"/>
<Button x:Name="OpenPartnerList" Content="..." Click="OpenPartnerList_Click"/>
<Button x:Name="OpenPartner" Content="O" Click="OpenPartner_Click"/>
<Button x:Name="ClearPartner" Content="X" Click="ClearPartner_Click"/>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<StackPanel Orientation="Vertical" >
<TextBox x:Name="TextBoxSelectedPartner" Template="{StaticResource entityTextBoxTemplate}" HorizontalAlignment="Center" VerticalAlignment="Center" />
<!--<Button x:Name="ChoosePartner" Click="ChoosePartner_Click" Content="Choose partner"/>
<DataGrid ItemsSource="{Binding Partners}" AutoGenerateColumns="True" />-->
</StackPanel>
</Window>
and view model:
public class ViewModel : BaseViewModel
{
private List<Partner> partners;
public List<Partner> Partners
{
get { return this.partners; }
}
private Partner partner;
public Partner Partner
{
get { return this.partner; }
set
{
this.partner = value;
NotifyPropertyChanged();
}
}
public ViewModel()
{
AppDbContext db = new AppDbContext();
this.partners = db.Partners.ToList();
db.Dispose();
}
}
I'd like to create TextBox with 3 buttons:
1 - for choosing Partner from some list
2 - for opening window with Partner's details
3 - for clearing TextBox
For this purpose I've created ControlTemplate "entityTextBoxTemplate": TextBox is for storing Partner.Title and hidden TextBlock is for storing Partner.Id. I assume that after choosing a Partner from list TextBox and TextBlock will be filled with Title and Id respectively, but it doesn't work and I don't know why. Can anybody help me to solve this issue?
Updated:
Partner is populated in this code:
private void PartnerListView_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
viewModel.Partner = ((PartnerListView)sender).SelectedPartner;
}
Updated 2:
My BaseViewModel:
public class BaseViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My fixed BaseViewModel:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I just forgot to specify implemented interface: ": INotifyPropertyChanged"
your class Partner may be INotifyPropertyChanged (I put you code for Title, and this is the same for other parameters of your object)
public class Partner : INotifyPropertyChanged
{
private string title;
public string Title
{
get { return this.title; }
set
{
if (this.title != value)
{
this.title = value;
this.NotifyPropertyChanged("Title");
}
}
}
public override string ToString()
{
return Title;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
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 items stored in the ItemSource property of a combobox, however, when the user types in a name that does not exist in the list I need it to create a new object and use that as the SelectedObject. I am pretty new to WPF and used to WindowsForms, so I might just be going about doing this the totally wrong way, any input is appreciated.
Xaml:
<Window x:Class="ComboExample.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">
<StackPanel>
<ComboBox x:Name="cboName" IsEditable="True" SelectionChanged="ComboBox_SelectionChanged"></ComboBox>
<DockPanel>
<Label>Selected Value</Label>
<TextBox Text="{Binding Name}"></TextBox>
</DockPanel>
<Button Click="Button_Click">Click Me</Button>
</StackPanel>
and code behind (which displays "value is null" if you type a new value in
class SomeClass
{
public SomeClass(string name) {this.Name = name;}
public string Name { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
cboName.ItemsSource = new SomeClass[] { new SomeClass("A"), new SomeClass("B") };
cboName.DisplayMemberPath = "Name";
cboName.SelectedItem = cboName.ItemsSource.OfType<SomeClass>().FirstOrDefault();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SomeClass value = cboName.SelectedValue as SomeClass;
if (value == null)
MessageBox.Show("No item is selected.");
else
MessageBox.Show("An item is selected.");
}
SomeClass empty = new SomeClass("");
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataContext = cboName.SelectedItem as SomeClass;
if (DataContext == null)
cboName.SelectedValue = DataContext = empty;
}
}
here one way to do it:
<StackPanel>
<ComboBox x:Name="cboName" ItemsSource="{Binding ComboBoxItems}" DisplayMemberPath="Name" SelectedItem="{Binding ComboBoxItems[0],Mode=OneTime}" IsEditable="True"/>
<DockPanel>
<Label>Selected Value</Label>
<TextBox Text="{Binding SelectedItem.Name,ElementName=cboName}"></TextBox>
</DockPanel>
<Button Click="Button_Click">Click Me</Button>
</StackPanel>
and the code behind :
public partial class MainWindow : Window
{
private ObservableCollection<SomeClass> _comboBoxItems;
public ObservableCollection<SomeClass> ComboBoxItems
{
get
{
return _comboBoxItems;
}
set
{
_comboBoxItems = value;
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
ComboBoxItems = new ObservableCollection<SomeClass>()
{
new SomeClass("First Name"),
new SomeClass("Second Name"),
new SomeClass("Third Name")
};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (!ComboBoxItems.Any(x => x.Name == cboName.Text))
{
ComboBoxItems.Add(new SomeClass(cboName.Text));
}
}
}
public class SomeClass
{
public SomeClass(string name) { this.Name = name; }
public string Name { get; set; }
}
To better manage your objects you may consider
defining a new property For the Selected ComboBox Item (Of Type SomeClass), and bind it to the ComboBox SelectedItem,
Use ObservableCollection instead of just list and Implement the INotifyPropertyChanges Interface.
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 know I am doing something wrong here but what. Please have a look and point out my error.
I will see "Peter" in my textbox but no "Jack" after the button click.
My class
namespace App
{
class Person : INotifyPropertyChanged
{
private string name;
public String Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
public Person()
{
Name = "Peter";
}
public void SetName(string newname)
{
Name = newname;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}
}
My XAML
<Window x:Class="test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:App"
Title="MainWindow" Height="400" Width="400">
<Grid>
<Grid.Resources>
<app:Person x:Key="person"/>
</Grid.Resources>
<TextBox Width="100" Height="26" Text="{Binding Source={StaticResource person}, Path=Name, Mode=TwoWay}" />
<Button Content="Button" Height="23" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
And my codebehind
public partial class MainWindow : Window
{
Person person;
public MainWindow()
{
InitializeComponent();
person = new Person();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
person.SetName("Jack");
}
}
Thanks.
You have two instances of Person. PropertyChanged is not null in the static resource
This isn't really what StaticResources are for. Get rid of the static resource, change the binding to:
{Binding Path=Name, Mode=TwoWay}
and add this to your constructor:
DataContext = person;
That object person in codebehind of MainWindow is not the same object you have binding to in XAML
If you want to use that object from resources you have to find it in code behind so something like this in constructor
person = (Person)Resources["person"];