Grouping in WPF - c#

I am trying to modify an existing code which allows to add articles by rows and remove them wherever needed, the problem is that I am trying to find a way to group articles of the same category. So when the user adds a new article of DVD category, it will be directly added to that category (I tried to take some ideas from here, but with no success: http://msdn.microsoft.com/en-us/library/ff407126.aspx
This is the code behind data:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfDataGridWithDataTable
{
public class Article
{
public Article ()
{
}
private int _modelNumber;
public int ModelNumber
{
get { return _modelNumber; }
set { _modelNumber = value; OnPropertyChanged("ModelNumber"); }
}
private string _modelName;
public string ModelName
{
get { return _modelName; }
set { _modelName = value; OnPropertyChanged("ModelName"); }
}
private decimal _unitCost;
public decimal UnitCost
{
get { return _unitCost; }
set { _unitCost = value; OnPropertyChanged("UnitCost"); }
}
private string _description ;
public string Description
{
get { return _description; }
set { _description = value; OnPropertyChanged("Description"); }
}
#region INotifyPropertyChanged Membres
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
public class ListArticles : ObservableCollection<Article >
{
public Article a;
public ListArticles()
{
a = new Article();
this.Add(a);
}
}
}
And here is the XAML code:
<Window x:Class="WpfDataGridWithDataTable.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfDataGridWithDataTable"
Title="Window1" Height="300" Width="300">
<Grid
Name="gridPanel">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<DataGrid
Grid.Column="0"
Name="dataGrid1"
AutoGenerateColumns="True"
CanUserAddRows="True"
CanUserDeleteRows="True"
CanUserResizeColumns="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"/>
<ListBox
Grid.Column="1"
Name="listBox1"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:Article}">
<StackPanel
Orientation="Horizontal">
<TextBlock
Width="100"
Margin="10"
Background="DarkBlue"
Foreground="White"
FontSize="14"
Text="{Binding ModelNumber}"/>
<TextBlock
Width="100"
Margin="10"
Background="DarkBlue"
Foreground="White"
FontSize="14"
Text="{Binding ModelName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Left"
Width="100"
Name="btnAdd"
Content="Add Item"
Click="btnAdd_Click">
</Button>
<Button
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Right"
Width="100"
Name="btnDelete"
Content="Delete Item"
Click="btnDelete_Click" >
</Button>
</Grid>
</Window>
And the code behind the form:
namespace WpfDataGridWithDataTable
{
/// <summary>
/// Logique d'interaction pour Window1.xaml
/// </summary>
public partial class Window1 : Window
{
private ListArticles myList;
public Window1()
{
InitializeComponent();
myList = new ListArticles();
this.DataContext = myList;
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
myList.Add(new Article());
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
myList.Remove(this.dataGrid1.SelectedItem as Article);
}
}
}
Thanks for your answers.

I finally resolved the problem, but I don't know why it didn't worked. The issue was located in the XAML code when binding the datagrid with the CollectionViewSource. I put:
<DataGrid Name="dataGrid1"
AutoGenerateColumns="True"
CanUserAddRows="True"
CanUserDeleteRows="True"
CanUserResizeColumns="True"
IsSynchronizedWithCurrentItem="True"
Grid.ColumnSpan="2"
ItemsSource="{Binding Source={StaticResource cvsListArticles}}">
The last line was the problem, when I simply put ItemsSource="{Binding}" it finally worked.
Here is the code for those who might have the same problem and want to benefit from it:
code behind data:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfDataGridWithDataTable
{
public class Article
{
public Article()
{
}
private int _modelNumber;
public int ModelNumber
{
get { return _modelNumber; }
set { _modelNumber = value; OnPropertyChanged("ModelNumber"); }
}
private string _modelName;
public string ModelName
{
get { return _modelName; }
set { _modelName = value; OnPropertyChanged("ModelName"); }
}
private decimal _unitCost;
public decimal UnitCost
{
get { return _unitCost; }
set { _unitCost = value; OnPropertyChanged("UnitCost"); }
}
private string _description;
public string Description
{
get { return _description; }
set { _description = value; OnPropertyChanged("Description"); }
}
#region INotifyPropertyChanged Membres
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
public class ListArticles : ObservableCollection<Article>
{
public Article a;
public ListArticles()
{
a = new Article();
this.Add(a);
}
}
}
The XAML code:
<Window x:Class="WpfDataGridWithDataTable.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication18"
Title="Window1" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="480" Width="760">
<Window.Resources>
<CollectionViewSource x:Key="cvsListArticles" Source="Article">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ModelName"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid
Name="gridPanel">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<DataGrid Name="dataGrid1"
AutoGenerateColumns="True"
CanUserAddRows="True"
CanUserDeleteRows="True"
CanUserResizeColumns="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}" Grid.ColumnSpan="2" >
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
<Button
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Left"
Width="100"
Name="btnAdd"
Content="Add Item"
Click="btnAdd_Click">
</Button>
<Button
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Right"
Width="100"
Name="btnDelete"
Content="Delete Item"
Click="btnDelete_Click" >
</Button>
<Button Content="Group" Grid.ColumnSpan="2" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="294,5,0,0" Name="GroupButton" VerticalAlignment="Top" Width="145" Click="GroupButton_Click" />
</Grid>
</Window>
The code behind the form:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfDataGridWithDataTable
{
public partial class Window1 : Window
{
private ListArticles myList;
public Window1()
{
InitializeComponent();
myList = new ListArticles();
this.DataContext = myList;
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
myList.Add(new Article());
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
myList.Remove(this.dataGrid1.SelectedItem as Article);
}
private void GroupButton_Click(object sender, RoutedEventArgs e)
{
ICollectionView cvsListArticles = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
if (cvsListArticles != null && cvsListArticles.CanGroup == true)
{
cvsListArticles.GroupDescriptions.Clear();
cvsListArticles.GroupDescriptions.Add(new PropertyGroupDescription("ModelName"));
}
}
}
}

Related

How to stop Label from stacking in wpf

I am creating a program where if you click the button it adds a new label. However the problem is that when you click the add button the label keeps getting stack on top of eachother instead of being a list
Here is the code
c#
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 firstwpfapp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void addTask(object sender, RoutedEventArgs e)
{
String val = input.ToString();
Label todo = new Label();
todo.Content = val;
List.Children.Add(todo);
}
}
}
xaml
...
<Window x:Class="firstwpfapp.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:firstwpfapp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Margin="-120,-142,0,0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0*"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="451*"/>
</Grid.ColumnDefinitions>
<StackPanel x:Name="Wrapper"Background="LightGray" Orientation="Horizontal"></StackPanel>
<TextBox x:Name="input" Grid.Column="2" HorizontalAlignment="Left" Height="31" Margin="106,198,0,0" TextWrapping="Wrap" Text="Enter here" VerticalAlignment="Top" Width="166"/>
<Button Content="Button" Grid.Column="2" HorizontalAlignment="Left" Margin="106,234,0,0" VerticalAlignment="Top" Width="166" Height="26" Click="addTask"/>
<Grid x:Name="List" Grid.Column="2" HorizontalAlignment="Left" Height="391" Margin="507,160,0,0" VerticalAlignment="Top" Width="385"/>
</Grid>
</Window>
the list keeps getting stack on top of another each time the button is pressed
You can go ahead and add your items to a ListView which will stack the items for you as well as include an ItemSource that we can bind to so it will create the rows for each new ToDo item for you:
Note: I have not tested the below; I'm on my Macbook.
Instead of your Wrapper StackLayout, replace it with:
<ListView Name="Wrapper" Grid.Row="0" Grid.ColumnSpan="3" ItemsSource="{Binding Tasks}" />
Now, create a new file called Task.cs which we will use when creating a new type of Task (add the below to the Task.cs file):
public class Task { public string task { get; set;} }
Have your MainWindow inherit from the INotifyPropertyChanged interface INotifyPropertyChanged
public partial class MainWindow : Window, INotifyPropertyChanged
Now update the rest of your code behind of MainWindow to:
private ObservableCollection<Task> _tasks;
//Tasks will store all of the tasks of type Task that we add to it
//as well as be bound to our ListView that will display whatever we add to our Tasks
public ObservableCollection<Task> Tasks
{
get
{
return _tasks;
}
set
{
if (value != _tasks)
{
_tasks = value;
OnPropertyChanged("Tasks");
}
}
}
//Here we implement OnPropertyChanged so our ObservableCollection can be notified
//whenever we have a new task added to or removed from Tasks (this is created when we implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
//Create a new Task and add it to our Tasks any time the addTask button is clicked
private void addTask(object sender, RoutedEventArgs e)
{
Tasks.Add(new Task(input.Text));
}
Replace the Grid
<Grid x:Name="List" Grid.Column="2" HorizontalAlignment="Left" Height="391" Margin="507,160,0,0" VerticalAlignment="Top" Width="385"/>
with a StackPanel:
<StackPanel x:Name="List" Grid.Column="2" HorizontalAlignment="Left" Height="391" Margin="507,160,0,0" VerticalAlignment="Top" Width="385"/>
Adding child elements to a grid stacks them on top of each other (as you have noticed)
A StackPanel adds new child elements below (or after) the previous child element.
Try this ...................
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Center"
Orientation="Horizontal"
Margin="0,50,0,0">
<TextBox Name="Input"
Width="300"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
<Button Name="AddBtn"
Content="Add"
Margin="20,0,0,0"
VerticalAlignment="Center"
Width="100"
Click="AddBtn_Click"/>
</StackPanel>
<ListView Name="ItemListView"
ItemsSource="{Binding Path=LabelItems, Mode=OneWay}"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Margin="20"/>
</Grid>
C# code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private ObservableCollection<string> _labels = new ObservableCollection<string>();
public ObservableCollection<string> LabelItems
{
get { return _labels; }
set { _labels = value; RaisePropertyChanged(); }
}
private void AddBtn_Click(object sender, RoutedEventArgs e)
{
if(Input.Text != "" && Input.Text != null)
{
LabelItems.Add(Input.Text);
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged([CallerMemberName]string name = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}

WPF Updating UI using Multiple Classes

I am teaching myself... I cannot understand why the UI won't update when a second class is involved. I am missing something basic and I don't get it.
In the first Class:
I have two ObservableCollections bound to two WPF ListViews, which is bound correctly and works.
I have a Command bound to a Button to move items from one Collection to the other, which works as expected.
In the second Class (backcode) I have implemented "Drag and Drop". On Drop I try to call the same Method (which is in the first Class and is used by the Button/Command. The Command is also in the first class).
On "Drag and Drop" the items are moved from one collection to the other (confirmed with Console.Writeline), however the UI doesn't update like it does with the Button/Command.
I believe the problem is that with "Drag and Drop" I am calling the Method from another class. I thought I could do that, but I must not be doing it right?
I have included everything from 4 files (xaml, backcode, class, relayCommand) so hopefully it is easy to reproduce. Can anyone tell me why & how to get this to work???
<Window x:Class="MultipleClassDragAndDrop.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:MultipleClassDragAndDrop"
xmlns:ViewModel="clr-namespace:MultipleClassDragAndDrop.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="716" Width="500">
<Window.Resources>
<ViewModel:MultiColumnViewModel x:Key="MultiColumnViewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Mode=OneWay, Source={StaticResource MultiColumnViewModel}}" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<Button Content="Test Command" Command="{Binding Test_Command}"/>
</StackPanel>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView1" Background="Black" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView1, UpdateSourceTrigger=PropertyChanged}" MouseMove="ListView1_MouseMove" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightPink" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<Grid Grid.Row="0" Grid.Column="2" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView2" Background="Black" MinHeight="300" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView2, UpdateSourceTrigger=PropertyChanged}"
MouseMove="ListView1_MouseMove"
AllowDrop="True" Drop="ListView2_Drop" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightBlue" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
BackCode
using MultipleClassDragAndDrop.ViewModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
namespace MultipleClassDragAndDrop
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
int lb_itemIndex = ListView1.SelectedIndex;
// Package the data.
DataObject data = new DataObject();
data.SetData("Int", lb_itemIndex);
data.SetData("Object", this);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
private void ListView2_Drop(object sender, DragEventArgs e)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()}");
base.OnDrop(e);
int index = (int)e.Data.GetData("Int");
// Call A Method In A Different Class
objMultiColumnViewModel.AddAndRemove(index);
e.Handled = true;
}
}
}
My ViewModel Class
using MultipleClassDragAndDrop.ViewModel.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Input;
namespace MultipleClassDragAndDrop.ViewModel
{
public class ActiveJob : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
public string _JobID;
public string JobID
{
get { return _JobID; }
set
{ _JobID = value; NotifyPropertyChanged("JobID"); }
}
public string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set
{ _CustomerName = value; NotifyPropertyChanged("CustomerName"); }
}
}
public partial class MultiColumnViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
//Test Command
private ICommand _Test_Command;
public ICommand Test_Command
{
get
{
if (_Test_Command == null)
{
_Test_Command = new RelayCommand<object>(ExecuteTest_Command, CanExecuteTest_Command);
}
return _Test_Command;
}
}
public bool CanExecuteTest_Command(object parameter)
{
return true;
}
public void ExecuteTest_Command(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
AddAndRemove(0);
Mouse.OverrideCursor = Cursors.Arrow;
}
public void AddAndRemove(int selectedIndex)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()} Index = {selectedIndex}\n");
ActiveJobListView2.Add(ActiveJobListView1[selectedIndex]);
ActiveJobListView1.RemoveAt(selectedIndex);
foreach (var item in ActiveJobListView1)
{
System.Console.WriteLine($"ActiveJobListView1: {item.JobID}, {item.CustomerName}");
}
System.Console.WriteLine($" ");
foreach (var item in ActiveJobListView2)
{
System.Console.WriteLine($"ActiveJobListView2: {item.JobID}, {item.CustomerName}");
}
}
public MultiColumnViewModel()
{
ActiveJobListView1 = new ObservableCollection<ActiveJob>();
ActiveJobListView2 = new ObservableCollection<ActiveJob>();
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB100", CustomerName = "Smith" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB101", CustomerName = "Jones" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB102", CustomerName = "Black" });
}
#region Properties
private ObservableCollection<ActiveJob> _ActiveJobListView1;
public ObservableCollection<ActiveJob> ActiveJobListView1
{
get { return _ActiveJobListView1; }
set
{
_ActiveJobListView1 = value;
NotifyPropertyChanged("ActiveJobListView1");
}
}
private ObservableCollection<ActiveJob> _ActiveJobListView2;
public ObservableCollection<ActiveJob> ActiveJobListView2
{
get { return _ActiveJobListView2; }
set
{
_ActiveJobListView2 = value;
NotifyPropertyChanged("ActiveJobListView2");
}
}
#endregion
}
}
When binding to a Collection, there are 3 kinds of ChangeNotification you need:
The Notification that informs the UI if something was added or removed from the Collection. That is the only kind of Notification ObservableCollection provides.
The Notification on the property exposing the ObservableCollection. Due to case 1 binding and the lack of a "add range", it is a bad idea to do bulk-modifications of a exposed List. Usually you create a new list and only then Expose it to the UI. In your case those would be the properties "ActiveJobListView1" and it's kind.
The Notification on every property of every type exposed in the collection. That would be "ActiveJob" in your case.
Some of those are often forgotten, with Case 2 being the most common case. I wrote a small introduction into WPF and the MVVM pattern a few years back. maybe it can help you here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf
You have issues with different instances of the same class.
Change:
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
To:
var objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
and it should work
EDIT:
What you are doing is strongly against MVVM principals.
EDIT-2
I had to do some modification to you code to make it work:
In your XAML:
<Window.DataContext>
<local:MultiColumnViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
In your MainWindow.cs:
public MainWindow()
{
InitializeComponent();
objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
}
private MultiColumnViewModel objMultiColumnViewModel;

Crash and bluescreen (ntoskrnl.exe) when pinging servers from c# wpf

I was writing a little tool for work to check ping latency to our servers in c# wpf. I've done a similar thing with Windows Forms and the occuring problem appears to be the same. After some time i get a bluescreen including memory dump. The problem was caused by ntoskrnl.exe and I have no idea why. All I do is ping some servers every 1000 ms.
Here's my c# code:
using MahApps.Metro.Controls;
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;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.Net.NetworkInformation;
using System.ComponentModel;
namespace NetworkChecker
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : MetroWindow
{
ObservableCollection<PingTarget> PingTargets = new ObservableCollection<PingTarget>();
PingTargetHost pth = new PingTargetHost();
public MainWindow()
{
pth.PingTargetList = new ObservableCollection<PingTarget>();
InitializeComponent();
PingTarget ptExample = new PingTarget { TargetName = "Google", TargetIP = "www.google.ch" };
pth.PingTargetList.Add(ptExample);
PingTargetListBox.ItemsSource = pth.PingTargetList;
}
private void AddNewTarget_Click(object sender, RoutedEventArgs e)
{
PingTarget newTarget = new PingTarget { TargetName = NewTargetNametb.Text, TargetIP = NewTargetIPtb.Text };
pth.PingTargetList.Add(newTarget);
}
}
public class PingTarget : INotifyPropertyChanged
{
public PingTarget()
{
PingCollection = new ObservableCollection<KeyValuePair<DateTime, long>>();
this.Active = true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private string _TargetName;
public string TargetName
{
get { return _TargetName; }
set { _TargetName = value; OnPropertyChanged("TargetName"); }
}
private string _TargetIP;
public string TargetIP
{
get { return _TargetIP; }
set { _TargetIP = value; OnPropertyChanged("TargetIP"); }
}
private ObservableCollection<KeyValuePair<DateTime, long>> _PingCollection;
public ObservableCollection<KeyValuePair<DateTime, long>> PingCollection
{
get { return _PingCollection; }
set { _PingCollection = value; OnPropertyChanged("PingCollection"); }
}
public long MaxPing
{
get
{
return PingCollection.Max(p => p.Value);
}
}
public long MinPing
{
get
{
return PingCollection.Min(p => p.Value);
}
}
public long AvgPing
{
get
{
return (long)PingCollection.Average(p => p.Value);
}
}
private bool _Active;
public bool Active
{
get { return _Active; }
set { _Active = value; OnPropertyChanged("Active"); }
}
}
public class PingTargetHost : INotifyPropertyChanged
{
public PingTargetHost(int PingInterval = 1000)
{
PingFrequency = PingInterval;
Ticker = new DispatcherTimer();
Ticker.Interval = new TimeSpan(0, 0, 0, 0, PingFrequency);
Ticker.Tick += Ticker_Tick;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
void Ticker_Tick(object sender, EventArgs e)
{
foreach (PingTarget pt in PingTargetList.Where(pts => pts.Active == true))
{
Ping pingSender = new Ping();
string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
int timeout = 10000;
byte[] buffer = Encoding.ASCII.GetBytes(data);
PingReply Result = pingSender.Send(pt.TargetIP, timeout, buffer);
string Pong = Result.RoundtripTime.ToString();
if (Result.Status == IPStatus.Success)
{
KeyValuePair<DateTime, long> ping = new KeyValuePair<DateTime, long>(DateTime.Now, Result.RoundtripTime);
pt.PingCollection.Add(ping);
}
if (Result.Status == IPStatus.TimedOut)
{
}
}
}
public void StopTicker()
{
Ticker.Stop();
}
public void StartTicker()
{
Ticker.Start();
}
private DispatcherTimer Ticker { get; set; }
public int PingFrequency { get; set; }
public ObservableCollection<PingTarget> PingTargetList { get; set; }
}
}
Here's my WPF code:
<Controls:MetroWindow x:Class="NetworkChecker.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
Title="Simple Network Checker" Height="600" Width="1000">
<Grid x:Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="5" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="1" >
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" />
</StackPanel>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="5" />
<RowDefinition Height="150" />
</Grid.RowDefinitions>
<ListBox x:Name="PingTargetListBox" DataContext="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Label Content="{Binding TargetName}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Separator Margin="2" Grid.Row="1" />
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox x:Name="NewTargetIPtb" Controls:TextBoxHelper.Watermark="Target IP" Controls:TextBoxHelper.UseFloatingWatermark="True" />
<TextBox x:Name="NewTargetNametb" Grid.Row="1" Controls:TextBoxHelper.Watermark="Target Name" Controls:TextBoxHelper.UseFloatingWatermark="True" />
<Button x:Name="AddNewTarget" Click="AddNewTarget_Click" Content="Hinzufügen" Grid.Row="2" />
</Grid>
</Grid>
<Grid Grid.Column="2" DataContext="{Binding ElementName=PingTargetListBox, Path=SelectedItem}">
<Grid.RowDefinitions >
<RowDefinition />
<RowDefinition Height="5" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox x:Name="DetailsPingListBox" ItemsSource="{Binding PingCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Content="{Binding Key}" />
<Label Content="{Binding Value}" Grid.Column="1" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Separator Grid.Row="1" Margin="2" />
<StackPanel Grid.Row="2">
<CheckBox x:Name="ActiveInactive" IsChecked="{Binding Active}" Content="Aktiv" />
<TextBlock>
Maximum: <TextBlock Text="{Binding MaxPing}" />
</TextBlock>
<TextBlock>
Minimum: <TextBlock Text="{Binding MinPing}" />
</TextBlock>
<TextBlock>
Average: <TextBlock Text="{Binding AvgPing}" />
</TextBlock>
</StackPanel>
</Grid>
</Grid>
Any ideas?
I'd really love this tool to work without bluescreens ;)
Thanks a lot for your help!
Mo

Progress bar isn't updating on custom splash screen

So I'm trying to have a splash screen that has a progress bar that increments as my application loads. Obviously I've had to edit code to remove any IP, thankfully most of this is simple enough that none of it really matters. But any help would be greatly appreciated.
Failed Solution 1
Here is the code for my application block:
public class App : Application
{
public void Run()
{
bool isLoaded = false;
ProgressSplashScreen splashScreen = new ProgressSplashScreen(() => { isLoaded = true; });
splashScreen.Show();
SpinWait.SpinUntil(() => isLoaded);
_bootStrapper = new BootStrapper(splashScreen);
_bootStrapper.Run();
splashScreen.Close();
}
}
Here is the code for my splash screen XAML:
<Window x:Class="Application.ProgressSplashScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Splash" BorderBrush="Transparent"
AllowsTransparency="True"
Icon="../../Resources/Icons/Icon.ico"
WindowStartupLocation="CenterScreen"
WindowStyle="None" Width="640" Height="520"
Background="Transparent" Name="SplashWindow" Topmost="True" >
<Grid>
<StackPanel Orientation="Vertical" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Image Source="../../Resources/Images/splash.jpg" Height="480" Width="640"/>
<Grid>
<ProgressBar Name="SplashProgress" Height="20" Minimum="0" Maximum="100"
BorderBrush="Transparent" />
<Label Name="SplashMessage" VerticalAlignment="Bottom"
HorizontalAlignment="Center"/>
</Grid>
</StackPanel>
</Grid>
</Window>
And the Code Behind for the Splash Screen XAML:
using System;
using System.Threading;
using System.Windows;
namespace Application
{
public partial class ProgressSplashScreen : Window
{
private SynchronizationContext _synchContext;
public ProgressSplashScreen(Action isLoaded)
{
this.Loaded += (sender, args) => isLoaded();
InitializeComponent();
_synchContext = SynchronizationContext.Current;
}
public void SetProgress(double progress, string message)
{
_synchContext.Send((state) =>
{
SplashProgress.Value = progress;
SplashMessage.Content = message;
}, null);
}
}
}
And Finally the BootStrapper Class that I am trying to make the update calls from:
namespace Application
{
internal sealed class BootStrapper : BaseMefBootstrapper
{
private ProgressSplashScreen _splash;
public BootStrapper(ProgressSplashScreen splashScreen)
{
_splash = splashScreen;
}
public void Run()
{
// removed do stuff code here
_splash.SetProgress(10, "Loading stuff...");
// removed do stuff code here
_splash.SetProgress(40, "Loading stuff...");
// removed do stuff code here
_splash.SetProgress(80, "Loading stuff...");
// removed do stuff code here
_splash.SetProgress(90, "Loading stuff...");
// removed do stuff code here
_splash.SetProgress(100, "Loading stuff...");
}
}
}
And so far my screen just sits there with an empty progress bar...
Failed Solution 2
ps... I've also tried
using System;
using System.Threading;
using System.Windows;
namespace Application
{
public partial class ProgressSplashScreen : Window, INotifyPropertyChanged
{
private Dispatcher _dispatcher;
public event PropertyChangedEventHandler PropertyChanged;
private string _progressMessage;
private double _progressValue;
public string ProgressMessage
{
get { return _progressMessage; }
set
{
_progressMessage = value;
OnPropertyChanged("ProgressMessage");
}
}
public double ProgressValue
{
get { return _progressValue; }
set
{
_progressValue = value;
OnPropertyChanged("ProgressValue");
}
}
public ProgressSplashScreen(Action isLoaded)
{
this.Loaded += (sender, args) => isLoaded();
InitializeComponent();
_dispatcher = Dispatcher.CurrentDispatcher;
}
public void SetProgress(double progress, string message)
{
_dispatcher.BeginInvoke(new Action(() =>
{
ProgressValue = progress;
ProgressMessage = message;
}), DispatcherPriority.ContextIdle, null);
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
with this as the XAML
<Window x:Class="Application.ProgressSplashScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Splash" BorderBrush="Transparent"
AllowsTransparency="True"
Icon="../../Resources/Icons/Icon.ico"
WindowStartupLocation="CenterScreen"
WindowStyle="None" Width="640" Height="520"
Background="Transparent" Name="SplashWindow" Topmost="True" >
<Grid>
<StackPanel Orientation="Vertical" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Image Source="../../Resources/Images/splash.jpg" Height="480" Width="640"/>
<Grid>
<ProgressBar Name="SplashProgress" Height="20" Minimum="0" Maximum="100"
BorderBrush="Transparent" Value="{Binding ProgressValue,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Label Name="SplashMessage" VerticalAlignment="Bottom" HorizontalAlignment="Center"
Content="{Binding ProgressMessage,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</Grid>
</StackPanel>
</Grid>
</Window>
Any help would be great! Thanks! Really surprised this is as hard as it seems right now...
So what I was talking about is :
MainWindow.xaml
<Window x:Class="SplashSxreenExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Splashscreen"
WindowStyle="none" ResizeMode="NoResize" ShowInTaskbar="False" WindowStartupLocation="CenterScreen"
Height="400" Width="700" Background="#FF292929" FontFamily="Century Gothic"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/SplashScrTemplate.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="AUto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Test Splash Screen" Foreground="LightGray" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="60"/>
<Label Grid.Row="1" Content="{Binding ProgressMessage}" Foreground="LightGray" FontStyle="Italic"/>
<ProgressBar Grid.Row="2" Height="3" Foreground="White" Style="{StaticResource FlatProgressBar}"
Value="{Binding ProgressValue}"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
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.ComponentModel;
namespace SplashSxreenExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _progressMessage;
private int _progressValue;
private BackgroundWorker _mWorker;
public MainWindow()
{
InitializeComponent();
_mWorker = new BackgroundWorker();
_mWorker.WorkerReportsProgress = true; //Allow reporting
_mWorker.ProgressChanged += _mWorker_ProgressChanged;
_mWorker.DoWork += _mWorker_DoWork;
_mWorker.RunWorkerAsync();
}
void _mWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker wkr = sender as BackgroundWorker;
wkr.ReportProgress(0, "Starting..."); //Reporting progress
//Here do your stuff
Thread.Sleep(500);
wkr.ReportProgress(10, "Loading stuff...");
//Here do your stuff
Thread.Sleep(1000);
wkr.ReportProgress(40, "Loading stuff 2...");
//Here do your stuff
Thread.Sleep(500);
wkr.ReportProgress(80, "Loading stuff 3...");
//Here do your stuff
Thread.Sleep(1500);
wkr.ReportProgress(90, "Loading stuff 4...");
//Here do your stuff
Thread.Sleep(2000);
wkr.ReportProgress(100, "Finished");
}
void _mWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressValue = e.ProgressPercentage; //Value
ProgressMessage = e.UserState.ToString(); //Message
}
#region Properties
public string ProgressMessage
{
get { return _progressMessage; }
set
{
_progressMessage = value;
OnPropertyChanged("ProgressMessage");
}
}
public int ProgressValue
{
get { return _progressValue; }
set
{
_progressValue = value;
OnPropertyChanged("ProgressValue");
}
}
#endregion
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
And the resource dictionnary (for the flat bar, just in case)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="FlatProgressBar" TargetType="{x:Type ProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Border BorderBrush="{x:Null}" BorderThickness="0" Background="{x:Null}" CornerRadius="0" Padding="0">
<Grid x:Name="PART_Track">
<Rectangle x:Name="PART_Indicator" HorizontalAlignment="Left" Fill="{Binding RelativeSource={RelativeSource AncestorType=ProgressBar},Path=Foreground}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
This is working pretty well for me, tell me if you have some difficulties.
Here is a ptrscr of the result :
I hope it could help you.
BR,
Bastien.

WPF User Control - Two way binding doesn't work

I have made a UserControl with a DependencyProperty and I would like to bind 2 way. But somehow this doesn't work. The "City"-property never gets set in the AddressViewModel when the property changes.
This is my UserControl:
XAML:
<UserControl x:Class="EasyInvoice.UI.CityPicker"
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"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="300"
DataContext="{Binding CityList, Source={StaticResource Locator}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox IsReadOnly="True" Margin="3" Text="{Binding SelectedCity.PostalCode, Mode=OneWay}" Background="#EEE" VerticalContentAlignment="Center" HorizontalContentAlignment="Right"/>
<ComboBox Margin="3" Grid.Column="1" ItemsSource="{Binding Path=Cities}" DisplayMemberPath="CityName" SelectedItem="{Binding SelectedCity}"/>
</Grid>
</UserControl>
Code behind:
using EasyInvoice.UI.ViewModel;
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 EasyInvoice.UI
{
/// <summary>
/// Interaction logic for CityPicker.xaml
/// </summary>
public partial class CityPicker : UserControl
{
public CityPicker()
{
InitializeComponent();
((CityListViewModel)this.DataContext).PropertyChanged += CityPicker_PropertyChanged;
}
private void CityPicker_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedCity")
SetCurrentValue(SelectedCityProperty, ((CityListViewModel)this.DataContext).SelectedCity);
}
public static readonly DependencyProperty SelectedCityProperty =
DependencyProperty.Register("SelectedCity", typeof(CityViewModel), typeof(CityPicker),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public CityViewModel SelectedCity
{
get
{
return (CityViewModel)GetValue(SelectedCityProperty);
}
set
{
SetCurrentValue(SelectedCityProperty, value);
}
}
}
}
This is where I use this control:
<UserControl x:Class="EasyInvoice.UI.NewCustomerView"
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"
mc:Ignorable="d"
Height="135" Width="450"
xmlns:xctk="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
xmlns:einvoice="clr-namespace:EasyInvoice.UI">
<UserControl.Resources>
<Style TargetType="xctk:WatermarkTextBox">
<Setter Property="Margin" Value="3"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<xctk:WatermarkTextBox Grid.ColumnSpan="2" Watermark="Voornaam" Text="{Binding FirstName}"/>
<xctk:WatermarkTextBox Grid.ColumnSpan="2" Watermark="Famillienaam" Grid.Column="2" Text="{Binding LastName}"/>
<xctk:WatermarkTextBox Grid.ColumnSpan="2" Watermark="Straat" Grid.Row="2" Text="{Binding Address.Street}"/>
<xctk:WatermarkTextBox Grid.ColumnSpan="1" Watermark="Huisnummer" Grid.Column="2" Grid.Row="2" Text="{Binding Address.HouseNr}"/>
<xctk:WatermarkTextBox Grid.ColumnSpan="1" Watermark="Busnummer" Grid.Column="3" Grid.Row="2" Text="{Binding Address.BusNr}"/>
<einvoice:CityPicker Grid.Row="3" Grid.ColumnSpan="4" SelectedCity="{Binding Address.City, Mode=TwoWay}"/>
<Button Grid.Row="5" Content="Opslaan en sluiten" Grid.ColumnSpan="4" Style="{StaticResource PopupButton}"/>
</Grid>
</UserControl>
And this is the ViewModel for "Address":
using EasyInvoice.Model;
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EasyInvoice.UI.ViewModel
{
public class AddressViewModel : ViewModelBase
{
private string _street;
private string _houseNr;
private string _busNr;
private CityViewModel _city;
public AddressViewModel(Address address)
{
LoadAddress(address);
}
public AddressViewModel() : this(new Address()) { }
private Address Address { get; set; }
public string Street
{
get
{
return _street;
}
set
{
if (_street == value)
return;
_street = value;
RaisePropertyChanged("Street");
}
}
public string HouseNr
{
get
{
return _houseNr;
}
set
{
if (_houseNr == value)
return;
_houseNr = value;
RaisePropertyChanged("HouseNr");
}
}
public string BusNr
{
get
{
return _busNr;
}
set
{
if (_busNr == value)
return;
_busNr = value;
RaisePropertyChanged("BusNr");
}
}
public string BusNrParen
{
get
{
return string.Concat("(", BusNr, ")");
}
}
public bool HasBusNr
{
get
{
return !string.IsNullOrWhiteSpace(_busNr);
}
}
public CityViewModel City
{
get
{
return _city;
}
set
{
if (_city == value)
return;
_city = value;
RaisePropertyChanged("City");
}
}
public void LoadAddress(Address address)
{
this.Address = address;
if(address == null)
{
_street = "";
_houseNr = "";
_busNr = "";
_city = new CityViewModel(null);
}
else
{
_street = address.StreetName;
_houseNr = address.HouseNr;
_busNr = address.BusNr;
_city = new CityViewModel(address.City);
}
}
}
}
When I'm debugging, I can see that this line is reached when I change the property in my UI:
SetCurrentValue(SelectedCityProperty, ((CityListViewModel)this.DataContext).SelectedCity);
But somehow, this never gets set:
public CityViewModel City
{
get
{
return _city;
}
set
{
if (_city == value)
return;
_city = value;
RaisePropertyChanged("City");
}
}
Also, I'm sure the viewmodel is wired up correctly, because I set a breakpoint at "HouseNr" and this works correctly.
Just in case there is not enough provided, the project can be found here: https://github.com/SanderDeclerck/EasyInvoice
From your code
public CityViewModel SelectedCity
{
get
{
return (CityViewModel)GetValue(SelectedCityProperty);
}
set
{
SetCurrentValue(SelectedCityProperty, value);
}
}
Change this
SetCurrentValue(SelectedCityProperty, value);
to this
SetValue(SelectedCityProperty, value);
Issue is in your binding. You have set DataContext of UserControl to CityListViewModel so binding is failing since binding engine is searching for property Address.City in CityListViewModel instead of AddressViewModel.
You have to explicitly resolve that binding using RelativeSource or ElementName.
SelectedCity="{Binding DataContext.Address.City,RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=UserControl}, Mode=TwoWay}"
OR
Give x:Name to UserControl say NewCustomer and bind using ElementName.
SelectedCity="{Binding DataContext.Address.City, ElementName=NewCustomer}"
Also you can avoid setting Mode to TwoWay since you have already specified that at time of registering of DP.

Categories

Resources