How to add a countdown timer to a child element in Wpf - c#

Im quite new to coding. So far I have a WPF application that when I press submit it creates the treeview but I wanted to add a countdown timer for each child item and have it display the time remaining next to the child item. The problem is the treeview doesn't update and I dont know how to assign a timer for each child item
using Microsoft.Azure.Cosmos.Core.Collections;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace Test_v2
{
public partial class MainWindow : Window
{
public int secondsCount = 100;
public MainWindow()
{
InitializeComponent();
DispatcherTimer disTmr = new DispatcherTimer();
disTmr.Tick += new EventHandler(disTmr_Tick);
disTmr.Interval = new TimeSpan(0, 0, 1);
disTmr.Start();
}
public void disTmr_Tick(object sender, EventArgs e)
{
secondsCount--;
}
List<TreeViewItem> folderList = new List<TreeViewItem>();
public void SubmitButton_Click(object sender, RoutedEventArgs e)
{
if (Folder.Text.Length == 0)
{
ErrorBlock.Text = "Please Enter Folder Name";
return;
}
if (Name.Text.Length == 0)
{
ErrorBlock.Text = "Please Enter a Name";
return;
}
TreeViewItem parent = new TreeViewItem();
for (int i = 0; i < folderList.Count; i++)
{
if (folderList[i].Header.ToString() == Folder.Text)
{
parent = folderList[i];
break;
}
}
if (folderList.Contains(parent))
{
FolderInArrayBlock.Text = "True";
TreeViewItem newChild = new TreeViewItem();
newChild.Header = Name.Text + secondsCount.ToString();
parent.Items.Add(newChild);
}
else
{
FolderInArrayBlock.Text = "false";
TreeViewItem? treeItem = null;
treeItem = new TreeViewItem();
treeItem.Header = Folder.Text;
folderList.Add(treeItem);
treeItem.Items.Add(new TreeViewItem() { Header = Name.Text + secondsCount.ToString()});
LearningItems.Items.Add(treeItem);
}
}
}
}

First of all, if you are using Wpf, you need to use MVVM approch if you want to make a sustainable and maintainable code. This means you need to seperate View funcionalities from Model funcionalities and use a ViewModel as a bridge to be able to communicate with those two things. In Wpf we should try to use Bindings and notifypropertychange to build the brige between View and ViewModel and not use control naming for later use in code behind .cs.(Code behind is the .cs file which belongs to .xaml file ex.: MainWindow.xaml.cs)
I recommend you to take a look at this page, which explains why its so important to use MVVM in your Wpf applications: MVVM pattern
I have created a sample project which demonstrate a proper approch for your task, in my opinion.
MainWindow.xaml
<Window x:Class="TreeViewWithCountDown.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:local="clr-namespace:TreeViewWithCountDown"
xmlns:localviewmodels="clr-namespace:TreeViewWithCountDown.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TreeView ItemsSource="{Binding Path=Items, Mode=OneWay}">
<!--We use TreeView Resources because we bind Items as ItemSource and Items is a List of StorageItems, which can be either FolderItem or FileItem.
TreeView can display the two types differently if we specify in the Resources-->
<TreeView.Resources>
<!--Here we specify how to display a FolderItem-->
<HierarchicalDataTemplate DataType="{x:Type localviewmodels:FolderItem}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}"
Margin="0 0 35 0"/>
</HierarchicalDataTemplate>
<!--Here we specify how to display a FileItem-->
<DataTemplate DataType="{x:Type localviewmodels:FileItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="FileNames"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Name}"
Margin="0 0 35 0"
Grid.Column="0"/>
<TextBlock Text="{Binding Path=CountdownTime}"
Margin="0 0 15 0"
Grid.Column="1">
</TextBlock>
</Grid>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
MainWindow.xaml.cs
using System.Windows;
namespace TreeViewWithCountDown
{
public partial class MainWindow : Window
{
private ViewModel _viewModel= new ViewModel();
public MainWindow()
{
InitializeComponent();
//Really important to define where to look for the binding properties
DataContext = _viewModel;
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Timers;
using TreeViewWithCountDown.ViewModels;
namespace TreeViewWithCountDown
{
public class ViewModel : INotifyPropertyChanged
{
private List<StorageItem> _items = new List<StorageItem>();
public List<StorageItem> Items
{
get => _items;
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged();
}
}
}
public ViewModel()
{
//Filling up our Items property which will be given to the View for display
Random random = new Random();
FileItem item0 = new FileItem("file0", random.Next(0,100));
FolderItem item1 = new FolderItem("folder1");
item1.Items.Add(item0);
FileItem item2 = new FileItem("file2", random.Next(0, 100));
FileItem item3 = new FileItem("file3", random.Next(0, 100));
Timer timer = new Timer(3000);
timer.Elapsed += Time_Elapsed;
timer.Start();
Items.Add(item1);
Items.Add(item2);
Items.Add(item3);
}
private void Time_Elapsed(object sender, ElapsedEventArgs e)
{
foreach (StorageItem item in Items)
{
if (item is FileItem fileItem)
{
fileItem.CountdownTime--;
}
else
{
//Reducing counters of Files in Folders
ReduceFileCountInFolders(item);
}
}
}
//A file can be nested in multiple folders so we can solve this with a recursive method
private void ReduceFileCountInFolders(StorageItem item)
{
if (item is FileItem fileItem)
{
fileItem.CountdownTime--;
}
else if (item is FolderItem folderItem)
{
if (folderItem.Items != null && folderItem.Items.Count > 0)
{
foreach (StorageItem storageItem in folderItem.Items)
{
ReduceFileCountInFolders(storageItem);
}
}
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
try
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
throw new Exception($"PropertyChanged event handler FAILED : {ex.Message}");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
StorageItem.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace TreeViewWithCountDown.ViewModels
{
public class StorageItem : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
public StorageItem(string name)
{
Name = name;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
try
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
throw new Exception($"PropertyChanged event handler FAILED : {ex.Message}");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
FileItem.cs
namespace TreeViewWithCountDown.ViewModels
{
public class FileItem : StorageItem
{
private int _countdownTime;
public int CountdownTime
{
get => _countdownTime;
set
{
if (_countdownTime != value && value > 0)
{
_countdownTime = value;
OnPropertyChanged();
}
}
}
public FileItem(string name, int num) : base(name)
{
CountdownTime = num;
}
}
}
FolderItem.cs
using System.Collections.Generic;
namespace TreeViewWithCountDown.ViewModels
{
public class FolderItem : StorageItem
{
private List<StorageItem> _items = new List<StorageItem>();
public List<StorageItem> Items
{
get => _items;
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged();
}
}
}
public FolderItem(string name) : base(name)
{
}
}
}
The final look: View
Hope this will help, if anything seems complicated, feel free to ask!

Related

WPF MVVM: update datagrid based on selecteditem of another datagrid

Class Diagram
I am developing a wpf application using MVVM pattern.I want to update Second datagrid based on selection of first datagrid & if there is any change in Itemsource of second datagrid I want to update that change while the selection of first datarid is retained. Can anybody help me with this.
The need is more or less similar to this DataGrid SelectionChanged MVVM. But Whenever there is a update in the first datagrid collection automatically the data in second datagrid must be updated for the selected item of first datagrid.
O8Lwl.png
Based on your comments I've created an minimal example which shows the binding. I hope this is what you were looking for.
MainWindow.xaml and code behind
<Window x:Class="WpfApp1.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"
mc:Ignorable="d"
Title="MainWindow" Height="400" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="300*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<DataGrid Grid.Column="0" Grid.Row="0" Width="Auto" ItemsSource="{Binding DeviceList}" SelectedItem="{Binding SelectedDevice}" AutoGenerateColumns="True" />
<DataGrid Grid.Column="1" Grid.Row="0" Width="Auto" DataContext="{Binding SelectedDevice}" ItemsSource="{Binding Path=FaultList}" AutoGenerateColumns="True"/>
<Button Content="Trigger DataGrid 1 update" Grid.Column="0" Grid.Row="1" Margin="10,10,10,10" Width="Auto" Height="Auto" Click="Button_Click"/>
</Grid>
</Window>
// Code behind in MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow : Window
{
private MainViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new MainViewModel();
DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
vm.AddDevice();
}
}
}
MainViewModel and MainModel
using System;
using System.Collections.ObjectModel;
using System.Linq;
using WpfApp1.ViewModels;
namespace WpfApp1
{
public class MainViewModel : ViewModelBase<MainModel>
{
private DeviceViewModel selectedDevice;
public ObservableCollection<DeviceViewModel> DeviceList
{
get { return Model.DeviceList; }
}
public DeviceViewModel SelectedDevice
{
get { return selectedDevice; }
set
{
selectedDevice = value;
RaisePropertyChanged("SelectedDevice");
}
}
public MainViewModel() : base(new MainModel())
{
}
public void AddDevice()
{
int rnd = new Random().Next(1, 100);
if (!Model.DeviceList.Any(x => x.Name == $"Device_{rnd}"))
Model.DeviceList.Add(new DeviceViewModel($"Device_{rnd}"));
RaisePropertyChanged("DeviceList");
}
}
}
using System.Collections.ObjectModel;
using WpfApp1.ViewModels;
namespace WpfApp1
{
public class MainModel
{
private ObservableCollection<DeviceViewModel> deviceList;
public ObservableCollection<DeviceViewModel> DeviceList
{
get { return deviceList; }
set { deviceList = value; }
}
public MainModel()
{
deviceList = new ObservableCollection<DeviceViewModel>();
}
}
}
DeviceViewModel.cs and Device.cs
using System.Collections.ObjectModel;
using WpfApp1.Models;
namespace WpfApp1.ViewModels
{
public class DeviceViewModel : ViewModelBase<Device>
{
private Fault selectedFault = null;
public string Name
{
get { return Model.Name; }
set
{
Model.Name = value;
RaisePropertyChanged("Name");
}
}
public string SerialNumber
{
get { return Model.Id.ToString(); }
}
public ObservableCollection<FaultViewModel> FaultList
{
get { return Model.FaultList; }
}
public Fault SelectedFault
{
get { return selectedFault; }
set
{
selectedFault = value;
RaisePropertyChanged("SelectedFault");
}
}
public DeviceViewModel() : base(new Device())
{
FaultList.CollectionChanged += FaultList_CollectionChanged;
}
public DeviceViewModel(string name) : this()
{
Name = name;
for (int i = 0; i < 5; i++)
Model.FaultList.Add(new FaultViewModel() { Name = $"Fault_{i} of {name}" });
RaisePropertyChanged("FaultList");
}
private void FaultList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("FaultList");
}
}
}
using System;
using System.Collections.ObjectModel;
namespace WpfApp1.Models
{
public class Device
{
private string name = "";
private Guid id;
private ObservableCollection<FaultViewModel> faultList;
public string Name
{
get { return name; }
set { name = value; }
}
public Guid Id
{
get { return id; }
set { id = value; }
}
public ObservableCollection<FaultViewModel> FaultList
{
get { return faultList; }
set { faultList = value; }
}
public Device()
{
this.id = new Guid();
this.faultList = new ObservableCollection<FaultViewModel>();
}
public Device(string name) : this()
{
this.name = name;
}
}
}
FaultViewModel.cs and Fault.cs
using WpfApp1.Models;
namespace WpfApp1
{
public class FaultViewModel : ViewModelBase<Fault>
{
public string Name
{
get { return Model.FaultName; }
set
{
Model.FaultName = value;
RaisePropertyChanged("Name");
}
}
public string Id
{
get { return Model.FaultId.ToString(); }
}
public FaultViewModel() : base(new Fault())
{
}
}
}
using System;
namespace WpfApp1.Models
{
public class Fault
{
private Guid faultId;
private string faultName;
public Guid FaultId
{
get { return faultId; }
set { faultId = value; }
}
public string FaultName
{
get { return faultName; }
set { faultName = value; }
}
public Fault()
{
this.faultId = new Guid();
}
}
}
Last but not least: ViewModelBase.cs
using System.ComponentModel;
namespace WpfApp1
{
public class ViewModelBase<T> : INotifyPropertyChanged
{
T model;
public T Model { get { return model; } }
public ViewModelBase(T model)
{
this.model = model;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null && !string.IsNullOrEmpty(propertyName))
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
If you run the app you can click the button which simulates the update of device list in code behind. You then can select a device and the second DataGrid will show the FaultList of this device.
Old answer
Update: Related to your comments and the class diagram:
First thing:
I can only see one List in class Device. I assume this is the source for the first DataGrid? So you want to display properties of objects of type Fault in the 1st DataGrid. If so, where is the source for the second DataGrid? Or are you missing a Collection property in class Fault?
Second:
For data bindings you have to use ObservableCollection which implements INotifyCollectionChanged. You can not use a List<>.
Third:
Without seeing your code I can only guess what's going wrong. Let's assume class Device contains an ObservableCollection<Fault> FaultList and class Fault contains an ObservableCollection<string> FaultDetails. DataGrid 1 displays the list of faults and if you select one of them, DataGrid 2 displays some fault details. In your DeviceViewModel you would have the ObservableCollection<Fault> FaultList and a property Fault SelectedFault. Now FaultList has to be the ItemSource of the 1st DataGrid and SelectedFault has to be bound to the DataGrid.SelectedItem property. The ItemSource of Datagrid 2 has to be FaultDetails and the DataContext has to be SelectedFault. Maybe you need to propagate the change of the property.
I have not tested this! A minimal executable example that shows the problem would be great.
Old answer:
It's been a while since i wrote my last WPF application, but it could be that you either raise the NotifyPropertyChanged event for the 2nd DataGrid at the wrong place, or you raise it for the wrong Property.
DataGrid has a SelectionChanged event. Maybe you are able to access your VM from there and raise the correct PropertyChanged event for your 2nd DataGrid.

DataBindings not updating with OnPropertyChanged

Building my first app with Xamarian.Forms. I have my basic menu and home page built with some labels and a button so that I can bind some data and a method and to make sure the logic code for my game is working. I got my data bindings working as far as the labels go, they appear on screen. However I was unsure if my bindings weren't updating or if my command wasn't binding. So I commented out the ICommand, removed the binding and put the method to advance a turn into the code behind my xaml. Even after this, the data is not updating when the button is clicked which leads me to believe it is a problem with my OnPropertyChanged and the data bindings. I've searched the web and related questions, I've implemented a couple different ways of writing the gets and sets for the bindings, wrote my OnPropertyChanged function a few different ways and still nothing happens when the toolbar button on the home page is clicked.
Here is my HomePageViewModel.cs containing INotifyPropertyChanged
***Edited to reflect changes made since getting the day value to update:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using Engine;
using Xamarin.Forms;
namespace TestApp
{
public class HomePageViewModel : INotifyPropertyChanged
{
public static Player _player = World.Player1;
public string Day = World.TrueDay.ToString();
public string MoneyValue = Convert.ToInt32(Math.Floor(World.Player1.PlayerMoney)).ToString();
public string CurrentLocation = _player.CurrentLocation.Name;
public HomePageViewModel()
{
OnTurn = new Command(execute: On1Turn);
}
public ICommand OnTurn { get; private set; }
public string CurrentDay
{
get { return Day; }
set { Day = value; OnPropertyChanged(); }
}
public string Money
{
get { return MoneyValue; }
set { MoneyValue = value; OnPropertyChanged(); }
}
public string PlayerLocation
{
get { return CurrentLocation; }
set { CurrentLocation = value; OnPropertyChanged(); }
}
void On1Turn()
{
World.TrueDay = World.TrueDay + 1;
CurrentDay = World.TrueDay.ToString();
World.Player1.PlayerMoney = World.Player1.PlayerMoney + 1000;
MoneyValue = Convert.ToInt32(Math.Floor(World.Player1.PlayerMoney)).ToString();
OnPropertyChanged(Money);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here is my HomePage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local1="clr-namespace:TestApp"
mc:Ignorable="d"
x:Class="TestApp.HomePage">
<ContentPage.BindingContext>
<local1:HomePageViewModel/>
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="+24hrs" Clicked="ToolbarItem_Clicked" />
</ContentPage.ToolbarItems>
<StackLayout Padding="20">
<StackLayout Orientation="Horizontal">
<Label Text="Money:" HorizontalOptions="Start"/>
<Label x:Name="lblPlayerMoney" Text="{Binding Money, Mode=OneWay}" HorizontalOptions="FillAndExpand"/>
</StackLayout>
<StackLayout Orientation="Horizontal" VerticalOptions="EndAndExpand" Margin="0,-40,0,0">
<Label Text="Current Location:" HorizontalOptions="CenterAndExpand"/>
<Label x:Name="lblPlayerLocation" Text="{Binding PlayerLocation, Mode=OneWay}" HorizontalOptions="CenterAndExpand"/>
</StackLayout>
<StackLayout Orientation="Horizontal" VerticalOptions="StartAndExpand">
<Label Text="Current Day:" HorizontalOptions="CenterAndExpand" Margin="30,0,0,0"/>
<Label x:Name="lblCurrentDay" Text="{Binding CurrentDay, Mode=OneWay}" HorizontalOptions="CenterAndExpand"/>
</StackLayout>
</StackLayout>
</ContentPage>
And the HomePage.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Engine;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TestApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
//BindingContext = new HomePageViewModel();
}
private void ToolbarItem_Clicked(object sender, EventArgs e)
{
World.TrueDay = World.TrueDay + 1;
World.Player1.PlayerMoney = World.Player1.PlayerMoney + 1000;
}
}
}
Any insight into the issue would be greatly appreciated as I'm new to Xamarian.Forms and the OnPropertyChanged feature in general. Thanks for the time!
EDIT******
Here is the World.cs were I set the properties if it helps
using System;
using System.Collections.Generic;
using System.Text;
namespace Engine
{
public class World
{
public static decimal TrueDay = 1;
//public string LocationText = Player1.CurrentLocation.Name.ToString();
public static Player Player1;
public static readonly List<Location> Locations = new List<Location>();
public const int LOCATION_ID_OSHAWA = 1;
public const int LOCATION_ID_TORONTO = 2;
public static void GenerateWorld()
{
PopulateLocations();
Player1 = new Player("Jordan", LocationByID(LOCATION_ID_OSHAWA), 5000);
}
private static void PopulateLocations()
{
Location oshawa = new Location(LOCATION_ID_OSHAWA, "Oshawa");
Location toronto = new Location(LOCATION_ID_TORONTO, "Toronto");
Locations.Add(oshawa);
Locations.Add(toronto);
}
public static Location LocationByID(int id)
{
foreach (Location location in Locations)
{
if (location.ID == id)
{
return location;
}
}
return null;
}
}
}
Not too understanding logic about project, but if want to change model data can do as follow
HomePage.xaml.cs:
namespace TestApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : ContentPage
{
HomePageViewModel homePageViewModel = new HomePageViewModel();
public HomePage()
{
InitializeComponent();
BindingContext = homePageViewModel ;
}
private void ToolbarItem_Clicked(object sender, EventArgs e)
{
homePageViewModel.CurrentDay = xxx ;
homePageViewModel.xxxxx = xxxx;
//Something like this can change model data
}
}
}
Here is a sample data binding discussion can be refer to.

Unable to use MVVM/binding correctly in the below code and having issues with Icommand property

Very new to WPF coding using MVVM. Tried making a simple calculator in WPF using MVVM. But unable to trigger the Icommand in the below code.If possible help me in this. Grateful if anybody can help me out.
View Code:
<Window x:Class="MVVMCalculator.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:local="clr-namespace:MVVMCalculator"
mc:Ignorable="d"
Title="Calculator" Height="350" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="85"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Display, Mode=OneWay}" IsReadOnly="True" TextWrapping="Wrap"
Grid.Row="0" Background="#E2E2E2" Margin="0,10,0,0" VerticalAlignment="Top"
Height="75" Width="250" HorizontalAlignment="Center" FontSize="22" FontWeight="Bold"
TextAlignment="Right">
<TextBox.Effect>
<DropShadowEffect/>
</TextBox.Effect>
</TextBox>
<ItemsControl Grid.Row="1" ItemsSource="{Binding Buttns}" Margin="15,15,15,10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" Rows="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Txt, Mode=TwoWay}" Command="{Binding Enter_number}"
FontSize="18" FontWeight="Bold" Height="50" Width="50" Background="#eef2f3"
BorderBrush="Black" BorderThickness="1.0" Name="number">
<Button.Effect>
<DropShadowEffect/>
</Button.Effect>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
ViewModel Code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MVVMCalculator
{
class ViewModel : INotifyPropertyChanged
{
Buttons btn = new Buttons();
private decimal operand1;
private decimal operand2;
private string operation;
private decimal result;
private string display;
private bool newDisplayRequired = false;
ObservableCollection<Buttons> buttns;
public ObservableCollection<Buttons> Buttns
{
get { return buttns; }
set { buttns = value; }
}
public decimal Result
{
get { return result; }
}
public decimal Operand1
{
get { return operand1; }
set { operand1 = value; }
}
public decimal Operand2
{
get { return operand2; }
set { operand2 = value; }
}
public string Operation
{
get { return operation; }
set { operation = value; }
}
public string Display
{
get { return display; }
set { display = value;
OnPropertyChanged("Display");
}
}
public ViewModel()
{
buttns = new ObservableCollection<Buttons>
{
new Buttons("1"), new Buttons("2"), new Buttons("3"),
new Buttons("C"), new Buttons("Back"), new Buttons("4"),
new Buttons("5"), new Buttons("6"), new Buttons("CE"),
new Buttons("%"), new Buttons("7"), new Buttons("8"),
new Buttons("9"), new Buttons("/"), new Buttons("*"),
new Buttons("0"), new Buttons("."), new Buttons("+"),
new Buttons("-"), new Buttons("=")
};
display = "0";
operand1 = 0;
operand2 = 0;
operation = "";
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ICommand enter_number;
public ICommand Enter_number
{
get
{
if(enter_number==null)
{
enter_number = new DelegateCommand<string>(MyAction, _canExecute);
}
return enter_number;
}
}
private static bool _canExecute(string button)
{
return true;
}
public void MyAction(string btn)
{
switch(btn)
{
case "C":
display = "0";
operand1 = 0;
operand2 = 0;
//operation = "";
break;
case ".":
if (!display.Contains("."))
{
Display = display + ".";
}
break;
case "Back":
if (display.Length > 1)
Display = display.Substring(0, display.Length - 1);
else Display = "0";
break;
default:
if (display == "0" || newDisplayRequired)
Display = btn;
else
Display = display + btn;
break;
}
}
}
}
Buttons Class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMCalculator
{
class Buttons:INotifyPropertyChanged
{
private string txt;
public string Txt
{
get { return txt; }
set { txt = value; }
}
public Buttons(string a)
{
txt = a;
}
public Buttons()
{
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MVVMCalculator
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
}
Since the Enter_number property is defined in the ViewModel class you need to use a {RelativeSource} to be able to bind to it:
<Button Content="{Binding Txt, Mode=TwoWay}"
Command="{Binding DataContext.Enter_number, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
FontSize="18" FontWeight="Bold" Height="50" Width="50" Background="#eef2f3"
BorderBrush="Black" BorderThickness="1.0" Name="number">
<Button.Effect>
<DropShadowEffect/>
</Button.Effect>
</Button>
The default DataContext of the Button is the current Buttons object in the ItemsSource collection of the ItemsControl and that's why your binding fails.

Binding Collection to ListBox [duplicate]

This question already has answers here:
How do I update an ObservableCollection via a worker thread?
(7 answers)
Closed 5 years ago.
I try to do some simple task (I guess so). I want to change GUI dynamically, from the for loop.
Let's see my XAML:
<Window x:Class="WpfApplication1.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 Name="MyPanel">
<TextBlock Text="{Binding MyValue}"></TextBlock>
<Button Click="Button_Click">OK</Button>
<ListBox Name="myList" ItemsSource="{Binding MyCollection}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding A}" Grid.Column="0"/>
<TextBlock Text="{Binding B}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
As you can see, I have the Textblock that shows numbers, button that is starting the program and the listbox, that should show the collection items.
After click on the button, the first textblock (bindes MyValue) shows dynamic values, but on the list box I get the next error:
"This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread."
I saw another answers for the error, but cannot understand how to implement it in my case.
Here the C# code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public static MyModel m;
public MainWindow()
{
m = new MyModel();
InitializeComponent();
MyPanel.DataContext = m;
}
bool flag = false;
private void Button_Click(object sender, RoutedEventArgs e)
{
flag = !flag;
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 5000000; i++)
{
if (flag == false) break;
m.MyValue = i.ToString();
m.MyCollection.Add(new ChartPoint { A = i, B = 2 * i });
}
});
}
}
public class MyModel : INotifyPropertyChanged
{
private string myValue;
public ObservableCollection<ChartPoint> MyCollection { get; set; }
public MyModel()
{
MyCollection = new ObservableCollection<ChartPoint>();
}
public string MyValue
{
get { return myValue; }
set
{
myValue = value;
RaisePropertyChanged("MyValue");
}
}
private void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ChartPoint
{
public int A { get; set; }
public int B { get; set; }
}
}
Thanks a lot!
I have changed the Button_Click code a bit, is this you want to achieve, please suggest:
private void Button_Click(object sender, RoutedEventArgs e)
{
flag = !flag;
var list = new List <ChartPoint>();
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 50000000; i++)
{
if (flag == false) break;
m.MyValue = i.ToString();
Dispatcher.BeginInvoke(new Action(() =>
{
m.MyCollection.Add(new ChartPoint
{
A = i,
B = 2 * i
});
}),
DispatcherPriority.Background);
}
});
}

Listivew with binding not updating

I've got a listview with binding and it's not updating. Can somebody find the bug? Wish I had some money, because I would offer a reward.
In this screen cap, the window on the right (Active Dinosaur List) is NOT updating when the status of a particular dinosaur is changing (note that when you click on the dinosaur (in this case, Nancy) it shows, correctly, that her status is, "Moving to food" while the Active Dinosaur List is showing her still Resting:
Here's all the relevant code, starting with the XAML for the window:
<Window x:Class="DinosaurIsland.ActiveDinosaurList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DinosaurIsland"
Title="ActiveDinosaurList" Height="850" Width="245" WindowStyle="SingleBorderWindow" Icon="/DinosaurIsland;component/Icon1.ico" ResizeMode="NoResize" >
<Window.Resources>
<local:EnergyBarColorConverter x:Key="EnergyBarColorConverter"/>
<local:DinoStatusConverter x:Key="DinoStatusConverter"/>
<DataTemplate x:Key="DinosaurInfo">
<StackPanel Orientation="Vertical" >
<Label Name="DinosaurName" Margin="0,0,0,-8" Content="{Binding Path=PersonalName}"/>
<Label Name="DinosaurSpecies" Margin="0,0,0,-8" FontStyle="Italic" Content="{Binding Path=Specie}"/>
<Label Name="DinosaurStatus" Margin="0,0,0,-8" Content="{Binding Path=State, Converter={StaticResource DinoStatusConverter}}"/>
<Label HorizontalAlignment="Center" Margin="0,0,0,-2" Content="Energy" />
<ProgressBar Name="Health" Margin="0,0,0,10" HorizontalAlignment="Center" VerticalAlignment="Top" Width="160" Height="15"
Foreground ="{Binding Path=Health, Converter={StaticResource EnergyBarColorConverter}}" Value="{Binding Path=Health}" />
<Separator/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Width="210">
<ListView x:Name="DinoListView" Width="207" ItemsSource="{Binding Path=Dinosaurs}" HorizontalAlignment="Left" Margin="3,0,0,0">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="Select Dinosaur" CellTemplate="{StaticResource DinosaurInfo}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
Here's the Dinosaur class:
using System;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
namespace DinosaurIsland
{
public class Dinosaur : INotifyPropertyChanged
{
public string _specie;
public string Specie
{
get { return _specie; }
set{_specie = value; RaisePropertyChanged("Specie");}
}
public int Age { get; set; }
public int Weight { get; set; }
public double Height { get; set; }
public int _health;
public int Health
{
get { return _health; }
set{_health = value; RaisePropertyChanged("Health");}
}
public double Water { get; set; }
public double FoodConsumed { get; set; }
public bool Sex { get; set; }
public string PersonalName { get; set; }
public System.Windows.Point Head = new System.Windows.Point();
public List<System.Windows.Point> Location { get; set; }
public double Length { get; set; }
public double Speed { get; set; }
public byte _state;
public byte State
{
get { return _state; }
set{_state = value; RaisePropertyChanged("State");}
}
public System.Windows.Point Goal = new System.Windows.Point();
public System.Windows.Point[] FoodLocation = new System.Windows.Point[5]; // The last five locations that the dino found food
public System.Windows.Point[] WaterLocation = new System.Windows.Point[5]; // The last five locations that the dino found water
// Constructor
public Dinosaur()
{
}
public event PropertyChangedEventHandler PropertyChanged;
//called when a property is changed
protected void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
}
Here's the ViewModel class:
using System;
using System.Collections.Generic;
using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace DinosaurIsland
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Dinosaurs = new ObservableCollection<Dinosaur>();
for(int i = 0; i < MainWindow.Dinosaurs.Count; i++)
this.Dinosaurs.Add(new Dinosaur()
{
PersonalName = MainWindow.Dinosaurs[i].PersonalName,
Specie = MainWindow.Dinosaurs[i].Specie,
Health = MainWindow.Dinosaurs[i].Health,
State = MainWindow.Dinosaurs[i].State
});
}
public event PropertyChangedEventHandler PropertyChanged;
//called when a property is changed
public void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
private ObservableCollection<Dinosaur> _dinoList = new ObservableCollection<Dinosaur>();
public ObservableCollection<Dinosaur> Dinosaurs
{
get { return _dinoList; }
set { _dinoList = value; RaisePropertyChanged("Dinosaurs"); }
}
}
}
Here's how the window is invoked:
// This is a global
public ViewModel vm = new ViewModel();
// ....
// Instantiate window
ViewModel vm = new ViewModel();
DinoListDialogBox.DataContext = vm;
DinoListDialogBox.Show();
That should be all the pieces to the puzzle. What am I missing?
Thanks... and I'll name a dinosaur after you.
Ok having looked at your source could get a solution for your use-case. I do suggest checking into MVVM properly.As it stands right now, Your project goes against MVVM in quite a few areas as I mentioned in chat.
Putting that aside with your current implementation to get the Dinosaurs list to be in sync with the ActiveDinosaurList View, these are the changes I made:
MainWindow.xaml.cs:
1) Switch Dinosaurs to an ObservableCollection<T> and a property. Such as
public static List<Dinosaur> Dinosaurs = new List<Dinosaur>();
to
public static ObservableCollection<Dinosaur> Dinosaurs { get; set; }
2) Add a static constructor to the MainWindow class to initialize the Dinosaurs property
static MainWindow() {
Dinosaurs = new ObservableCollection<Dinosaur>();
}
ViewModel.cs
3) Switch the Dinosaurs property to be a pass-thru to the static property in MainWindow and remove the backing collection. Such as
private ObservableCollection<Dinosaur> _dinoList = new ObservableCollection<Dinosaur>();
public ObservableCollection<Dinosaur> Dinosaurs
{
get { return _dinoList; }
set { _dinoList = value; RaisePropertyChanged("Dinosaurs"); }
}
to
public ObservableCollection<Dinosaur> Dinosaurs {
get {
return MainWindow.Dinosaurs;
}
}
4) Finally add a hook to listen to CollectionChanged on MainWindow.Dinosaurs from ViewModel and RaisePropertyChanged on it's Dinosaurs property.
so switch:
public ViewModel()
{
this.Dinosaurs = new ObservableCollection<Dinosaur>();
for(int i = 0; i < MainWindow.Dinosaurs.Count; i++)
this.Dinosaurs.Add(new Dinosaur()
{
PersonalName = MainWindow.Dinosaurs[i].PersonalName,
Specie = MainWindow.Dinosaurs[i].Specie,
Health = MainWindow.Dinosaurs[i].Health,
State = MainWindow.Dinosaurs[i].State
});
}
to
public ViewModel() {
MainWindow.Dinosaurs.CollectionChanged += (sender, args) => RaisePropertyChanged("Dinosaurs");
}
That's it. Running your simulation now, When I forwarded the time, I could see the Status on the ActiveDinosaurs list getting updated fine.
Inside your bindings use UpdateSourceTrigger=PropertyChanged.
So your label would look like this: <Label Name="DinosaurStatus" Margin="0,0,0,-8" Content="{Binding Path=State, Converter={StaticResource DinoStatusConverter} UpdateSourceTrigger=PropertyChanged}" />.

Categories