I recently started to learn the MVVM Pattern and created a simple application to test a few things.
I have a simple View with:
ListBox holding ObservableCollection of Items
Delete Button
New Button
TextBox for Item Description
TextBox for Item Value
Everything works except for the fact that, if i'm updating the item description the ListBox entry isn't updating. I read some articles about this, so i think it has something to do with CollectionChanged isn't called. I tried some possible solutions to this problem, but none of them worked. So maybe there is something generally wrong with my approach.
Hopefully someone can help me with this problem.
Model/Item.cs
internal class Item : INotifyPropertyChanged
{
#region Fields
private string value;
private string description;
#endregion
#region Constructors
public Item()
{
}
public Item(string value, string description) {
this.description = description;
this.value = value;
}
#endregion
public String Value
{
get
{
return value;
}
set
{
this.value = value;
OnPropertyChanged("Value");
}
}
public String Description
{
get
{
return description;
}
set
{
description = value;
OnPropertyChanged("Description");
}
}
#region Overrides
public override string ToString()
{
return description;
}
#endregion String Override
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
ViewModel/MainViewModel.cs
...
private ObservableCollection<Item> items;
private Item selectedItem;
public ObservableCollection<Item> Items {
get
{
return items;
}
set
{
items = value;
OnPropertyChanged("Items");
}
}
public Item SelectedItem {
get
{
return selectedItem;
}
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
...
View/MainWindow.xaml
...
<Button Content="New" Command="{Binding NewCommand}" />
<Button Content="Delete" Command="{Binding DeleteCommand}" />
<ListBox x:Name="lbxItems" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<TextBox Text="{Binding SelectedItem.Description}" />
<TextBox Text="{Binding SelectedItem.Value}" />
...
with this ItemTemplate it should work
<ListBox Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Value}" Margin="0,0,10,0/>
<TextBlock Text="{Binding Path=Description}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
How you generate a PropertyChangedEvent in your model class?
try this:
internal class Range : INotifyPropertyChanged
{
private string _value;
public string Value
{
get { return _value; }
set
{
if (_value == value) return;
_value = value;
OnPropertyChanged("Value");
}
}
//Do same for the Description property
//Do not forgot implement INotifyPropertyChanged interface here
}
I hope this help you
Oh! After your update it is clearly. You does not use any datatemplate for listbox item, so WPF calls the ToString() method for show item without DT. But when you update any property, WPF does not know about this because object does not changing.
Use Datatemplate or try call OnPropertyChanged with empty string.
Related
I'm having an issue databinding to a property that is two levels deep in a collection.
The object is structured as such
Collection
--Data
--Name
--Collection
--Name
--Data
So essentially its a collection within a collection, and I can't seem to bind to the name within the contained collection.
I can bind to all the properties of the initial collection, including the collection within it, but as soon as I try to bind to the name property of the internal collection I get bad bindings.
XAML Portion
<TabControl ItemsSource="{Binding Path=BMOverlayCollection}" Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="4">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock
Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<TextBlock
Text="{Binding Graphics.Name}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
C# Portion
public class BMGraphicsOverlayCollection : INotifyPropertyChanged, IEnumerable
{
#region Event Property
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private ObservableCollection<NamedOverlay> _namedOverlays;
public ObservableCollection<NamedOverlay> NamedOverlays { get { return _namedOverlays; } set { _namedOverlays = value; OnPropertyChanged(); } }
public BMGraphicsOverlayCollection()
{
_namedOverlays = new ObservableCollection<NamedOverlay>();
}
public void Add(string name, GraphicsOverlay overlay)
{
NamedOverlay mOverlay = new NamedOverlay();
mOverlay.Name = name;
mOverlay.Overlay = overlay;
_namedOverlays.Add(mOverlay);
}
public void Add(string name, GraphicsOverlay overlay, IList<MapGraphic> graphics)
{
NamedOverlay mOverlay = new NamedOverlay();
mOverlay.Name = name;
mOverlay.Overlay = overlay;
mOverlay.Graphics = new ObservableCollection<MapGraphic>(graphics);
_namedOverlays.Add(mOverlay);
}
public IEnumerator GetEnumerator()
{
return _namedOverlays.GetEnumerator();
}
}
public class NamedOverlay : INotifyPropertyChanged
{
#region Event Property
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private string _name;
public string Name { get { return _name; } set { _name = value; OnPropertyChanged(); } }
private GraphicsOverlay _overlay;
public GraphicsOverlay Overlay { get { return _overlay; } set { _overlay = value; OnPropertyChanged(); } }
private ObservableCollection<MapGraphic> _graphics;
public ObservableCollection<MapGraphic> Graphics { get { return _graphics; } set { _graphics = value; OnPropertyChanged(); } }
}
}
And the MapGraphic pertinent part
private string _name;
public string Name
{
get
{
if (_name == null)
{
return "Unnamed";
} else
{
return _name;
}
}
set
{
_name = value; OnPropertyChanged();
}
}
Thanks in advance for the help.
According to your code, Your data structure was NamedOverlays[].Graphics[].Name, but your binding path was NamedOverlays[].Graphics.Name. That's the reason of the bad binding. Change
<TextBlock Text="{Binding Graphics.Name}" />
to (if you want to show all names in Graphics)
<ListBox ItemsSource="{Binding Graphics}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
or (if you just want to show 1st name in graphics)
<TextBlock Text="{Binding Graphics[0].Name}" />
will fix the problem.
I know I should use the MVVM pattern but I'm trying to get step by step closer to it. So here is my Listbox:
<ListBox x:Name="BoardList" ItemsSource="{Binding notes}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBox IsReadOnly="True" ScrollViewer.VerticalScrollBarVisibility="Visible" Text="{Binding text}" TextWrapping="Wrap" Foreground="DarkBlue"></TextBox>
<AppBarButton Visibility="{Binding visibility}" Icon="Globe" Click="OpenInBrowser" x:Name="Link"></AppBarButton>
<AppBarButton Icon="Copy" Click="Copy"></AppBarButton>
<AppBarButton Icon="Delete" Click="Delete"></AppBarButton>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the Mainpage.xaml.cs I declare the following:
ObservableCollection<BoardNote> notes = new ObservableCollection<BoardNote>();
So if I understood this right I don't need to care about the "INotifyCollectionChanged" stuff because I'm using an observablecollection?
So I got for example a textbox like this:
<Textbox x:Name="UserInputNote" Placeholdertext="Type in a text for your note"></Textbox>
And a button to Add the new note to the ObservableCollection and the click event is just like this:
notes.Add(new BoardNote(UserInputNote.Text));
So now the UI should update every time the user clicks the button to save a new note. But nothing happens. What did I do wrong?
If you need it here is the BoardNote class:
class BoardNote
{
public string text
{
get; set;
}
public BoardNote(string text)
{
this.text = text;
}
public Visibility visibility
{
get
{
if (text.StartsWith("http"))
return Visibility.Visible;
else
return Visibility.Collapsed;
}
}
}
You need to implement INotifyPropertyChanged. Here's one way of doing it.
Create this NotificationObject class.
public class NotificationObject : INotifyPropertyChanged
{
protected void RaisePropertyChanged<T>(Expression<Func<T>> action)
{
var propertyName = GetPropertyName(action);
RaisePropertyChanged(propertyName);
}
private static string GetPropertyName<T>(Expression<Func<T>> action)
{
var expression = (MemberExpression)action.Body;
var propertyName = expression.Member.Name;
return propertyName;
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then your BoardNote class will inherit it this way:
class BoardNote : NotificationObject
{
private string _text
public string Text
{
get {return _text;}
set
{
if(_text == value) return;
_text = value;
RaisePropertyChanged(() => Text);
}
}
public BoardNote(string text)
{
this.text = text;
}
public Visibility visibility
{
get
{
if (text.StartsWith("http"))
return Visibility.Visible;
else
return Visibility.Collapsed;
}
}
}
i have a view that have a list view with data template
i need to set style on the selected item
but i need also when the selected item is been changed from the code it modify the selected item in the view
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" Width="300" Height="50" TextAlignment="Center"/>
<ListView Grid.Row="1" ItemsSource="{Binding List, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate >
<Border BorderThickness="1" BorderBrush="White">
<Grid Height="20" Width="30" >
<TextBlock Text="{Binding Name}"/>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
there is a list view and textblock
i need when the selectedItem changed it changed the the background of the selected item
here is the viewmodel
public class MainViewModel : ViewModelBase
{
private Item selectedItem;
public ObservableCollection<Item> List { get; set; }
string text;
public string Text
{
get { return text; }
set
{
text = value;
OnPropertyChanged("Text");
}
}
public Item SelectedItem
{
get { return selectedItem; }
set{
if (value.Name != "Test1")
{
selectedItem = value;
Text = value.Name;
}
else
{
Text = string.Format("Test1 was selected but the selected item is {0}", selectedItem==null?"null":selectedItem.Name);
}
OnPropertyChanged("SelectedItem");
}
}
public MainViewModel()
{
List = new ObservableCollection<Item>()
{
new Item("Test1","Val1"),new Item("Test2","Val2"),new Item("Test3","Val3"),new Item("Test4","Val"),
};
OnPropertyChanged("List");
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(params string[] propertyNames)
{
if (PropertyChanged != null)
{
foreach (var propertyName in propertyNames)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
}
}
public class Item : ViewModelBase
{
public string Name { get; set; }
public string Value { get; set; }
public Item(string name, string val)
{
Name = name;
Value = val;
OnPropertyChanged("Name");
}
}
note that when the Test1 Item selected the selected item didnot changed but in the view Test1 is marked as selected
At the point your MainViewModel.SelectedItem setter is called by the view, the view has already updated its selected item in the list. The binding simply informs the VM of this fact. The fact that you don't set MainViewModel.selectedItem means nothing to the view.
You would think that raising OnPropertyChanged("SelectedItem"); would force the view to re-evaluate its selected item, but in practice this does not work. I assume is down to some optimization within WPF or to prevent cyclic binding updates. (Remember you setter is already being called as part of a binding update, and you are trying to update the binding again)
If you wish to prevent something being selected in the view, then you need to disable it within the view, before it gets down to the VM. Here is one way of doing this.
I'm getting search results from the Bing API and adding each article to List and trying to bind it to a ListView in XAML but no results appear in the HubSection.
private List<NewsArticle> bNews = new List<NewsArticle>();
where NewsArticle is defined as:
public class NewsArticle : INotifyPropertyChanged
{
private string Description;
public event PropertyChangedEventHandler PropertyChanged;
public string Description1
{
get { return Description; }
set { Description = value; }
}
private string Link;
public string Link1
{
get { return Link; }
set { Link = value; }
}
private string Title;
public string Title1
{
get { return Title; }
set { Title = value;
NotifyProperyChanged("Title");
}
}
public void NotifyProperyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
And here is the XAML showing my HubSection and my bindings.
<HubSection DataContext="{Binding bNews}" Name="newsHub" Header="Hello World">
<DataTemplate>
<ListView ItemsSource="{Binding}" Margin="10" Name="ListBoxRss">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title1}" Tag="{Binding Link1}" TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</HubSection>
I've looked around for other examples and was even able to get it to work properly in a Panorama style app but am having trouble getting it to work properly inside a HubSection. Any insight would be wonderful.
for the past couple of weeks I've been trying to get the EditableTextBlock (from codeproject) working on my TreeView.
The control has a property IsInEditMode which when set to true changes it to a TextBox.
The TreeView is virtualized and declared as follows:
<TreeView x:Name="treeEnvironment"
Margin="0,29,0,0" BorderThickness="0"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
</TreeView>
The TreeView uses the ItemsSource property to get it's data and the value of this is always a single instance of a class (lets call it A). This class contains a list of instances of another type (lets call it B). And this last class contains a list of instances of yet another class (lets call it `C). This is how it looks like in the code:
class A
{
public String Name;
public ObservableCollection<B> Items;
}
class B
{
public String Name;
public ObservableCollection<C> Items;
}
class C
{
public String Name;
public bool IsRenaming;
}
For each of these three classes there is an HierarchicalDataTemplate defined in MainWindow.Resources as follows:
<DataTemplate DataType="{x:Type C}">
<StackPanel Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" Click="C_Rename_Click" />
</ContextMenu>
</StackPanel.ContextMenu>
<v:EditableTextBlock Text="{Binding Path=Name}" IsInEditMode="{Binding Path=IsRenaming, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type B}" ItemsSource="{Binding Path=Items, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type A}" ItemsSource="{Binding Path=Items, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<Image Source="icons/icon_A.png" Width="16" Height="16" />
<TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
None of the DataTemplate have keys so that it is applied automatically.
The event that is triggered when the rename MenuItem of C's context menu is clicked, is defined as follows:
private void C_Rename_Click(object sender, RoutedEventArgs e)
{
C instance = (sender as FrameworkElement).DataContext as C;
if (instance != null) {
instance.IsRenaming = true;
} else {
MessageBox.Show("DEBUG: C_Rename_Click(" + sender.ToString() + ", " + e.ToString() + ") : instance == null");
}
}
The problem is that the EditableTextBlock does not turn into a TextBox when the IsRenaming property is set to true on the instance of C that was chosen to be renamed.
The EditableTextBlock works just fine when I place it as a normal control.
My guess is that it has to do with virtualization. Any help would be appreciated.
Thank you for your time, best regards,
100GPing100.
class A, B, C need to implement INotifyPropertyChanged for any changes made to them to be propagated to the UI. You can either implement it in each class individually or have a base class implement INPC and derive your classes from this base class.
Something like:
public class MyBaseViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class A : MyBaseViewModel {
private string _name;
public string Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
private ObservableCollection<B> _items;
public ObservableCollection<B> Items {
get {
return _items;
}
set {
_items = value;
OnPropertyChanged("Items");
}
}
}
public class B : MyBaseViewModel {
private string _name;
public string Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
private ObservableCollection<C> _items;
public ObservableCollection<C> Items {
get {
return _items;
}
set {
_items = value;
OnPropertyChanged("Items");
}
}
}
public class C : MyBaseViewModel {
private string _name;
public string Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
private bool _isRenaming;
public bool IsRenaming {
get {
return _isRenaming;
}
set {
_isRenaming = value;
OnPropertyChanged("IsRenaming");
}
}
}
Now when you change IsRenaming in your code, you will see the update propagate to the UI and the TextBlock switch to a TextBox.
Side-note
Please have a look at MVVM. If you're not sure about it. Get to learn it slowly cos it helps UI development in WPF quite a bit.