ObservableCollection data binding during runtime - c#

I have a slight issue. I am making a calendar application with some listbox elements. Each calendar view retrieves it's "calendar events" from a dictionary where TKey = DateTime and TValue = ObservableCollection <CalendarEvent>. Now this works fine for any calendar day where there are predefined events already. I can data bind the listbox to a property that contains a reference to the dictionary entry of that particular calendar day. However another feature of my application should be the ability to add events during runtime. What I have done right now is, if there is no dictionary key present for that particular calendar day it just sets the Events property to null and then I change it during runtime if an event was added for that day, unfortunately it doesn't seem to like that, it doesn't "bind" properly or so to say.
Here is the code
public CalendarDayView(DateTime date)
{
DataContext = this;
Date = date;
Events = CalendarRepository.Instance.Entries.ContainsKey(date) ? CalendarRepository.Instance.Entries[date] : null;
}
public DateTime Date { get; set; }
public ObservableCollection<CalendarEvent> Events { get; set; }
/// <summary>
/// This method will set the listbox item source to the ObservableCollection if it hasn't been set already
/// </summary>
public void UpdateItemSource()
{
if (Events == null)
// This is the part that doesn't do anything unfortunately
Events = CalendarRepository.Instance.Entries[Date];
}
XAML markup
<ControlTemplate TargetType="{x:Type local:CalendarDayView}">
<Border BorderBrush="Gray" BorderThickness="0.2" Width="100" Height="100">
<Grid Name="contentGrid">
<ListBox
Name="entriesListBox" Background="LightYellow" FontSize="10"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Events}">
</ListBox>
<!-- Date display below -->
<TextBlock
Name="dateTextBlock" Text="{Binding Date, StringFormat={}{0:dd-MMM}, UpdateSourceTrigger=PropertyChanged}"
FontFamily="Segoe UI Light" FontSize="18" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="5"/>
</Grid>
</Border>
</ControlTemplate>

I don't see you raising the PropertyChanged event anywhere to notify the view of binding changes. You should implement INotifyPropertyChanged on the CalendarDayView model and raise the implemented PropertyChanged event in your property setters that are used as binding source (Events in this case).
The following code shows a simple example, but it might be better to add the PropertyChanged functionality to a base model class.
public class CalendarDayView : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<CalendarEvent> _events;
public ObservableCollection<CalendarEvent> Events
{
get { return _events; }
set
{
_events = value;
RaisePropertyChanged("Events");
}
}
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}

Related

Listview not updating on propertychange

When i add/remove items from the list the listview is registered on, the item gets added/removed accordingly. But when i change a property of the list, resulting in a different ToString() value, the Listview doesn't update the change accordingly. If i reload the data after a restart of the app from a xml file, the ListView shows it's items accordingly. So i think i can exclude an issue with my ToString method. Or is it an issue that I'm using ToSTring() at all?
Does anyone know the solution to this issue?
window.xaml:
<Window x:Class="WpfApplication1.MainWin"
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:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
DataContext="MainWindowViewModel"
Title="Baronieverwaltung für DSA" Height="1000" Width="1500"
WindowStartupLocation="CenterScreen"
WindowStyle="ThreeDBorderWindow">
<GroupBox Grid.Row="7" Grid.ColumnSpan="4" Header="Angestellte">
<ListView Height="200" ItemsSource="{Binding DieBaronie.Angestellte, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="{Binding SelectedAngestellterIndex}">
MainWindowViewModel.cs:
public class MainWindowViewModel : INotifyPropertyChanged
{
public Baronie DieBaronie { get; set; }
private void MethodThatChangesListViewItem()
{
if (SelectedAngestellterIndex > -1)
{
DieBaronie.Angestellte[SelectedAngestellterIndex].FunktionWarenschau = true;
}
//I found some threads where the solution was some variation of
//those NotifyPropertyChanged... but none work :(
NotifyPropertyChanged("DieBaronie.Angestellte");
NotifyPropertyChanged("DieBaronie");
NotifyPropertyChanged("");
NotifyPropertyChanged(null);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
DieBaronie.cs:
public class Baronie
{
public ObservableCollection<Angestellter> Angestellte { get; set; }
Angestellter.cs:
public class Angestellter : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private Boolean _FunktionWarenschau { get; set; }
public Boolean FunktionWarenschau
{
get
{
return _FunktionWarenschau;
}
set
{
//if i add a break point here, the debugger stops here as expected - with the correct value
_FunktionWarenschau = value;
NotifyPropertyChanged();
}
}
//Method doesn't even get called after the change :(
public override string ToString()
{
String val = Name + " ";
if (_FunktionWarenschau)
{
val += "(Warenschau)";
}
return val;
}
Like you suggested, the issue is with ToString() - this is not a property, so the WPF binding engine is not aware of any need to refresh the view.
In addition, with more complex MVVM scenarios, I believe it is convention to use Properties anyway, as you may build out your views to display more complex data (e.g. images) or customize the layout of your data further (e.g. panel of images + strings).
To solve your problem, I would recommend:
Create a property in your ViewModel to bind to. Here, you could simply bind to FunktionWarenschau and Name. Alternatively, you can create a new string property and have FunktionWarenschau either update your string property or simply call NotifyPropertyChanged with the new property name passed along.
Create a DataTemplate for your ListView (untested code to give you a flavor)
<ListView Height="200"
ItemsSource="{Binding DieBaronie.Angestellte, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding SelectedAngestellterIndex}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FunktionWarenschau}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

UWP Gridview binding to View Model

I'm using a GridView in a UserControl to display a five by four square of graphical buttons that allow selection of a Lesson.
This is in a Windows 8.1 Store App that I'm upgrading to Windows 10 UWP.
I previously used Tap and Right-Tap actions to select a Lesson or activate the CommandBar to perform related actions for a Lesson through the SelectionChanged event. However, there have been changes to how Interactions now work under Windows 10, I have been unable to get the Gridview to work at all with binding the SelectedItem to the selected LessonButton in the view model, nor the SelectionChanged and ItemClick events for such purposes. The Gridview selections behaviour doesn't work, as once an item is selected it is never deselected. So finally, I've taken a different tack and am trying Tap and Right-Tap events for the Gridview Items. However the issue is, that no matter which way I approach it, I can't get Binding to work correctly.
So I have an object called LessonButton:
public class LessonButton : INotifyPropertyChanged
{
//public LessonButton() { }
public LessonButton(SolidColorBrush inBackground, bool inComplete, double inHeight, int inNumber, bool inSelected, bool inStarted,
Status inState, double inWidth)
{
...
Started = inStarted;
...
}
...
private bool _started;
public bool Started
{
get { return _started; }
set { if (_started != value) { _started = value; OnPropertyChanged(); } }
}
...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
It is added to an observable collection in the View Model:
public class LessonsViewModel : INotifyPropertyChanged
{
public ObservableCollection<LessonButton> Lessons { get; } = new ObservableCollection<LessonButton>();
private LessonButton _selectedLessonButton;
public LessonButton SelectedLessonButton
{
get { return _selectedLessonButton; }
set { if (_selectedLessonButton != value) { _selectedLessonButton = value; OnPropertyChanged(); } }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In a user control I set the DataContext with:
<UserControl.DataContext>
<classes:LessonsViewModel/>
</UserControl.DataContext>
..and I then have a GridView defined as:
<GridView x:Name="LessonGridView" ItemContainerStyle="{StaticResource GridViewItemStyle}" ItemsSource="{Binding Lessons}"
SelectionMode="Single" IsItemClickEnabled="False" SelectedItem="{Binding Path=SelectedLessonButton, Mode=TwoWay}">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid HorizontalChildrenAlignment="Left" MaximumRowsOrColumns="5" Orientation="Horizontal" VerticalChildrenAlignment="Top"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
With the GridView item format defined in a ControlTemplate as part of the GridViewItemStyle.
I've tried to access the LessonButton variables in various ways using Binding and xBind, but could only get the program to run with the ControlTemplate using this XAML:
<Image Grid.Row="1" Grid.Column="1" Width="{StaticResource BadgeSize}"
Height="{StaticResource BadgeSize}" HorizontalAlignment="Right" VerticalAlignment="Top"
Opacity="{Binding Started, Converter={StaticResource OpacityConverterTrueValueIsVisible}}"
Source="/Assets/SelectionButtonGroup/square310x310logobw.png" Stretch="Uniform"/>
The Converter simply returns a 1 or 0 depending upon the value of the bool Started.
Although this code work, it is not correct somehow and Visual Studio reports an unknown error and states it cannot find the Started property. In fact it can't find any of the properties of LessonButton and I've been unable to find the correct syntax for exposing them, even with x:Bind code such as:
{x:Bind LessonViewModel.Lessons.LessonButton.Selected}
..or versions thereof, using casting etc.
I'm using Visual Studio 2017 Enterprise, which reports the aforementioned errors and displays wavy lines over the entire ControlTemplate with an error where it cannot find another Converter artefact that isn't even related to this code.. which in itself, I find extremely irritating. Is it me or does the XAML Intellisence in VS seem very flaky, in that it gives up and reports false errors if it can't identify the root cause of a real one?
Ideally I'd like the Gridview SelectedItem to bind with the ViewModel. But even trying actions via Tap events I can't get the binding to correctly expose LessonButton properties in the ControlTemplate XAML.
Any help would be greatly appreciated.
You shouldn't be using the ItemContainerStyle to Bind your LessonButton variables to. The ItemContainerStyle is used to style the Item with selection marks, its hover and pressed states etc.
You should instead use a DataTemplate stored inside your UserControl's resources like so:
<Grid>
<Grid.Resources>
<DataTemplate x:Name="GridViewTemplate">
<TextBlock Text="{Binding LessonName}">
</DataTemplate>
</StackPanel.Resources>
<GridView x:Name="GridView"
ItemsSource="{Binding Lessons}"
ItemTemplate="{StaticResource GridViewTemplate}">
</GridView>
</Grid>
Then give your DataTemplate a name (above "GridViewTemplate") and set it as the ItemTemplate of your GridView.

Bound TextBox does not update

I have a ComboBox bound to an ObservableCollection of objects (with several properties). The Combo Box accurately displays the desired property of all objects and I can select any item from the Combo as expected.
<ComboBox Height="23" Name="comboBox1" Width="120" Margin="5" ItemsSource="{Binding Issues}" DisplayMemberPath="Issue" SelectedValuePath="Issue" SelectedValue="{Binding Path=Issues}" IsEditable="False" SelectionChanged="comboBox1_SelectionChanged" LostFocus="comboBox1_LostFocus" KeyUp="comboBox1_KeyUp" Loaded="comboBox1_Loaded" DropDownClosed="comboBox1_DropDownClosed" IsSynchronizedWithCurrentItem="True" />
I have a series of text boxes which are supposed to display other properties of the selected object. This works fine too.
<TextBox Height="23" Name="textBox5" Width="59" IsReadOnly="True" Text="{Binding Issues/LastSale, StringFormat={}{0:N4}}" />
<TextBox Height="23" Name="textBox9" Width="90" IsReadOnly="True" Text="{Binding Path=Issues/LastUpdate, Converter={StaticResource TimeConverter}}" />
BUT... The properties of ObservableCollection are updated in the Code-Behind on a regular basis and I make a change to the OC by either adding or removing a dummy object in it every time the properties are updated. (I found this simpler than other solutions).
BUT...the data in the TextBoxes DO NOT change! :-( If I select a different object from the ComboBox I get updated info, but it does not change when the OC is changed.
The OC is composed of a bunch of these Objects:
public class IssuesItems
{
public String Issue { get; set; }
public Double LastSale { get; set; }
public DateTime LastUpdate { get; set; }
...
}
The OC is defined as:
public ObservableCollection<IssuesItems> Issues { get; set; }
and instantiated:
this.Issues = new ObservableCollection<IssuesItems>();
What am I doing wrong? Everything I read says that when the LastSale and LastUpdate properties are changed in the OC (and I do something to force an update of the OC) the data in the text boxes ought to change.
ObservableCollection implements INotifyCollectionChanged which allows GUI to refresh when any item is added or deleted from collection (you need not to worry about doing it manually).
But like i mentioned this is restricted to only addition/deletion of items from collection but if you want GUI to refresh when any underlying property gets changed, your underlying source class must implement INotifyPropertyChanged to give notification to GUI that property has changed so refresh yourself.
IssuesItems should implement INPC interface in your case.
Refer to this - How to implement INotifyPropertyChanged on class.
public class IssuesItems : INotifyPropertyChanged
{
private string issue;
public string Issue
{
get { return issue; }
set
{
if(issue != value)
{
issue= value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Issue");
}
}
}
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Implement other properties just like Issue as mentioned above.

How do I read Text property of silverlight toolkit NumericUpDown control?

I want to read value entered in the NumericUpDown control. How do i read it?
XAML Layout is follows
<StackPanel Style="{StaticResource StackPanelStyle_LableValue}">
<TextBlock Style="{StaticResource TextBlockStyle}"
Text="{Binding Path=ViewItem.Addition, Source={StaticResource LocalizedStrings }}" />
<inputToolkit:NumericUpDown Style="{StaticResource NumericUpdownStyle_Addition}"
Value="{Binding Items.RightSpecGlass.Addition, Mode=TwoWay}"
TabIndex="8" />
</StackPanel>
You can use
numericUpDown.Value; // To get decimal value of control
or
numericUpDown.Text; // To get value as string of control
Well, Since you have bind your view context, I think there is no reason to avoid get NumericUpDown's value except :
1- Maybe you forgot to initialize those classes or properties Items and/or RightSpecGlass
2- Your class doesn't implement INotifyPropertyChanged to raise when any control's value change in view. Addition property has to raise property change event in its setter.
public event PropertyChangedEventHandler PropertyChanged;
public virtual void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private int _addition;
public Int32 Addition
{
get { return _addition; }
set
{
_addition= value;
RaisePropertyChanged("Addition");
}
}
hope this help.

WPF, update TextBlock when CheckBox checked

I have a TreeView where each item has a checkbox. I want a TextBlock to be updated whenever an item is checked or unchecked in the TreeView. The TextBlock's Text should be bound to the CheckedVersions property on my DataContext so that when I read the CheckedVersions property, it gives me a string representing all the checked items in the TreeView. The checked items should be represented in a semicolon-separated string. What would be the best way to do this? I have the following XAML:
<XmlDataProvider Source="XmlData/Versions.xml" XPath="//*[count(*)=0]"
x:Key="versionsXml"
IsInitialLoadEnabled="True" IsAsynchronous="False" />
<HierarchicalDataTemplate x:Key="versionTemplate">
<CheckBox Focusable="False" IsChecked="{Binding Path=IsChecked}"
Content="{Binding Path=Name, Mode=OneTime}"/>
</HierarchicalDataTemplate>
<TreeView x:Name="trv_version"
ItemsSource="{Binding Path=Versions, Mode=OneWay}"
ItemTemplate="{StaticResource versionTemplate}" />
<TextBlock x:Name="txb_version" Text="{Binding Path=CheckedVersions}"
TextWrapping="Wrap" />
Each item in my TreeView is an instance of my VersionViewModel class, which implements INotifyPropertyChanged and notifies when the IsChecked property changes. It seems like I should be able to hook into that so that when IsChecked changes on a VersionViewModel instance in the TreeView, CheckedVersions updates. Maybe if I set UpdateSourceTrigger on the Text binding in the TextBlock? What should I set it to, though?
I think that your tree view model should "know" all the VersionViewModels and then all you need to do is register to the propertychanged event and set the "CheckedVersions" property according to the change.
something like that:
public class treeViewModel : INotifyPropertyChanged
{
public List<VersionViewModel> CurrentVersionViewModel { get; protected set; }
public void AddNewVersionViewModel(VersionViewModel vvm)
{
CurrentVersionViewModel.Add(vvm);
vvm.PropertyChanged += new PropertyChangedEventHandler(
(obj,propEventArgs) =>
{
if (propEventArgs.PropertyName=="IsChecked")
{
// CheckedVersions change logic according to the new value (this is just the concept)
CheckedVersions += (obj as VersionViewModel).IsChecked;
}
}
);
}
public string CheckedVersions { get { return _CheckedVersions; } set { _CheckedVersions = value; RaisePropertyChanged("CheckedVersions"); } }
private string _CheckedVersions;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string prop)
{
if (PropertyChanged!=null)
{
PropertyChanged(this,new PropertyChangedEventArgs(prop));
}
}
#endregion
}
public class VersionViewModel : INotifyPropertyChanged
{
public bool IsChecked { get; set; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}

Categories