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
Related
I am struggling to stop, resume and start the timer using wpf in c# windows application.
I have these button on my xaml but need some logic around c# windows application to achieve this. Secondly, I must have a status bar that will display to the user. When the timer was stop, resume and start as in windows application on my wpf.
How do I create an object in json to serialize this information?
This is my frontend XAML code:
<Window x:Class="PingApplication.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:PingApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="750">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Grid.Row="3" HorizontalAlignment="Right"
MinWidth="80" Margin="3" Content="Start" />
<Button Grid.Column="1" Grid.Row="3" HorizontalAlignment="Left"
MinWidth="80" Margin="3" Content="Stop" />
<Button Grid.Column="1" Grid.Row="3" HorizontalAlignment="Center"
MinWidth="80" Margin="3" Content="Resume" />
<Label Name="lblTime" FontSize="48" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Window>
And this is my c# backend code:
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.Windows.Threading;
namespace PingApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timer_Tick;
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
lblTime.Content = DateTime.Now.ToLongTimeString();
}
}
}
`
An example of a timer wrapper and its usage:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Input;
namespace Core2022.SO.Juju22Nimza
{
public class WpfTimer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private readonly Timer timer = new Timer();
private readonly Stopwatch stopwatch = new Stopwatch();
public DateTime Now => DateTime.Now;
public TimeSpan Elasped => stopwatch.Elapsed;
public WpfTimer()
{
timer.Interval = 50;
timer.Elapsed += OnTick;
timer.Start();
stopwatch.Start();
}
private void OnTick(object? sender, ElapsedEventArgs? e)
{
if (PropertyChanged is PropertyChangedEventHandler propertyChanged)
{
propertyChanged(this, NowArgs);
propertyChanged(this, ElaspedArgs);
}
}
public static PropertyChangedEventArgs NowArgs { get; } = new PropertyChangedEventArgs(nameof(Now));
public static PropertyChangedEventArgs ElaspedArgs { get; } = new PropertyChangedEventArgs(nameof(Elasped));
public static RoutedUICommand StartCommand { get; } = new RoutedUICommand("Timer Start", "TimerStart", typeof(WpfTimer));
public static RoutedUICommand ResumeCommand { get; } = new RoutedUICommand("Timer Resume", "TimerResume", typeof(WpfTimer));
public static RoutedUICommand RestartCommand { get; } = new RoutedUICommand("Timer Restart", "TimerRestart", typeof(WpfTimer));
public static RoutedUICommand ResetCommand { get; } = new RoutedUICommand("Timer Reset", "TimerReset", typeof(WpfTimer));
public static ExecutedRoutedEventHandler ExecuteCommand { get; } = (_, e) =>
{
if (e.Parameter is WpfTimer timer)
{
if (e.Command == StartCommand)
{
timer.stopwatch.Start();
}
else if (e.Command == ResumeCommand)
{
timer.stopwatch.Stop();
}
else if (e.Command == RestartCommand)
{
timer.stopwatch.Restart();
}
else if (e.Command == ResetCommand)
{
timer.stopwatch.Reset();
}
else return;
timer.OnTick(null, null);
}
};
public static CanExecuteRoutedEventHandler CanExecuteCommand { get; } = (_, e) =>
{
if (e.Parameter is WpfTimer timer)
{
if (e.Command == StartCommand)
{
e.CanExecute = !timer.stopwatch.IsRunning;
}
else if (e.Command == ResumeCommand)
{
e.CanExecute = timer.stopwatch.IsRunning;
}
else if (e.Command == RestartCommand)
{
e.CanExecute = true;
}
else if (e.Command == ResetCommand)
{
e.CanExecute = true;
}
}
};
public static WpfTimer Default { get; } = new WpfTimer();
}
}
<Window x:Class="Core2022.SO.Juju22Nimza.WpfTimerWindow"
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:Core2022.SO.Juju22Nimza"
mc:Ignorable="d"
Title="WpfTimerWindow" Height="450" Width="800">
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:WpfTimer.StartCommand}"
Executed="{x:Static local:WpfTimer.ExecuteCommand}"
CanExecute="{x:Static local:WpfTimer.CanExecuteCommand}"/>
<CommandBinding Command="{x:Static local:WpfTimer.ResetCommand}"
Executed="{x:Static local:WpfTimer.ExecuteCommand}"
CanExecute="{x:Static local:WpfTimer.CanExecuteCommand}"/>
<CommandBinding Command="{x:Static local:WpfTimer.RestartCommand}"
Executed="{x:Static local:WpfTimer.ExecuteCommand}"
CanExecute="{x:Static local:WpfTimer.CanExecuteCommand}"/>
<CommandBinding Command="{x:Static local:WpfTimer.ResumeCommand}"
Executed="{x:Static local:WpfTimer.ExecuteCommand}"
CanExecute="{x:Static local:WpfTimer.CanExecuteCommand}"/>
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Padding="15 5" Margin="5" Content="Start"
Command="{x:Static local:WpfTimer.StartCommand}"
CommandParameter="{x:Static local:WpfTimer.Default}"/>
<Button Padding="15 5" Margin="5" Content="Resume"
Command="{x:Static local:WpfTimer.ResumeCommand}"
CommandParameter="{x:Static local:WpfTimer.Default}"/>
<Button Padding="15 5" Margin="5" Content="Restart"
Command="{x:Static local:WpfTimer.RestartCommand}"
CommandParameter="{x:Static local:WpfTimer.Default}"/>
<Button Padding="15 5" Margin="5" Content="Reset"
Command="{x:Static local:WpfTimer.ResetCommand}"
CommandParameter="{x:Static local:WpfTimer.Default}"/>
</StackPanel>
<StackPanel>
<TextBlock FontSize="48" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Elasped, Source={x:Static local:WpfTimer.Default}}"/>
<TextBlock FontSize="48" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Now, Source={x:Static local:WpfTimer.Default}, StringFormat=T}"/>
</StackPanel>
</Grid>
</Window>
dont know about resume but timer.stop();.resume is might be start to
I want to implement undo and redo of inkcanvas strokes.
I want to implement redo and undo that can operate multiple times in a row.
I don't know where is the problem with my code.
Please help me.
My code is as follows:
xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<InkCanvas x:Name="inkCanvas"
Grid.Column="0" Grid.ColumnSpan="2"
Grid.Row="0" Grid.RowSpan="7"
Width="Auto" Height="Auto" EditingMode="Ink"
IsHitTestVisible="True"
Background="LightSeaGreen"
UseCustomCursor="True"
Cursor="Pen"/>
<Button x:Name="btn_Test1"
Grid.Row="0"
Grid.Column="1"
Width="100"
Height="50"
Content="pen"
Cursor="Hand"
Tag="Test1"
Click="Button_Click" />
<Button x:Name="btn_Test3"
Grid.Row="1"
Grid.Column="1"
Width="100"
Height="50"
Content="clear"
Tag="Test3"
Click="Button_Click" />
<Button x:Name="btn_Test4"
Grid.Row="2"
Grid.Column="1"
Width="100"
Height="50"
Content="UnDo"
Tag="Undo"
Click="Button_Click" />
<Button x:Name="btn_Test5"
Grid.Row="3"
Grid.Column="1"
Width="100"
Height="50"
Content="ReDo"
Tag="Redo"
Click="Button_Click" />
</Grid>
code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Media;
namespace InkCanvasUndoRedo
{
public partial class MainWindow : Window
{
public Stack<DoStroke> DoStrokes { get; set; }
public Stack<DoStroke> UndoStrokes { get; set; }
private bool handle = true;
public MainWindow()
{
InitializeComponent();
DoStrokes = new Stack<DoStroke>();
UndoStrokes = new Stack<DoStroke>();
inkCanvas.DefaultDrawingAttributes.FitToCurve = true;
inkCanvas.DefaultDrawingAttributes.Color = Color.FromArgb(255, 255, 255, 255);
inkCanvas.Strokes.StrokesChanged += Strokes_StrokesChanged;
}
private void Strokes_StrokesChanged(object sender, StrokeCollectionChangedEventArgs e)
{
if (handle)
{
DoStrokes.Push(new DoStroke
{
ActionFlag = e.Added.Count > 0 ? "ADD" : "REMOVE",
Stroke = e.Added.Count > 0 ? e.Added[0] : e.Removed[0]
});
}
}
public void Undo()
{
handle = false;
if (DoStrokes.Count > 0)
{
DoStroke dos = DoStrokes.Pop();
if (dos.ActionFlag.Equals("ADD"))
{
inkCanvas.Strokes.Remove(dos.Stroke);
}
else
{
inkCanvas.Strokes.Add(dos.Stroke);
}
UndoStrokes.Push(dos);
}
handle = true;
}
public void Redo()
{
handle = false;
if (UndoStrokes.Count > 0)
{
DoStroke dos = UndoStrokes.Pop();
if (dos.ActionFlag.Equals("ADD"))
{
inkCanvas.Strokes.Add(dos.Stroke);
}
else
{
inkCanvas.Strokes.Remove(dos.Stroke);
}
}
handle = true;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
switch ((sender as Button).Tag)
{
case "Test1":
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
break;
case "Test3":
inkCanvas.Strokes.Clear();
break;
case "Undo":
Undo();
break;
case "Redo":
Redo();
break;
}
}
}
public struct DoStroke
{
public string ActionFlag { get; set; }
public Stroke Stroke { get; set; }
}
}
The result:
Write: 123
Click: undo->undo->redo->redo->undo
Expected: 3 disappear
Actual: 1 disappears
In your redo Method you do
DoStroke dos = UndoStrokes.Pop();
if (dos.ActionFlag.Equals("ADD"))
{
inkCanvas.Strokes.Add(dos.Stroke);
}
else
{
inkCanvas.Strokes.Remove(dos.Stroke);
}
You should probably call DoStrokes.Push(dos); at the end of this. In a similar way as is done in the Undo-method.
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;
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.
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"));
}
}
}
}