WPF CommandParameter binding fails in ToolBar? - c#

Here's the situation: It's a bit hard to describe, so skip to the steps to recreate and copy/paste the code into a new project if you want. ListViewModel contains a list of ViewModel (Items) and a list of ICommand (Actions). MainWindow has a ToolBar that binds to the Actions, a ListView that binds to the Items, and a ContextMenu that binds to the actions within the ListView. In my implementation of ICommand (Command.cs), I have added the ability to insert custom code (the OnIsVisible property) that checks if the command Visibility should be Visible or Collapsed. This code works great for the Actions in the ToolBar and ContextMenu until you open the ContextMenu. Then the CommandParameter for the ToolBar is forever null, except when the ContextMenu is open.
Steps to recreate:
Select an item in the ListView
Click "Show When Selected" in the ContextMenu
Select another item in the ListView
At this point, the CommandParameter binding will always be NULL to the command object. So the "Show When Selected" button in the ToolBar will no longer appear.
Code:
In a new WPF application project called "NullParameter", create/edit the following files...
MainWindow.xaml:
<Window x:Class="NullParameter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected"
Value="{Binding IsSelected}" />
</Style>
<Style x:Key="contextMenuItemStyle"
TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding Header}" />
<Setter Property="Command"
Value="{Binding}" />
<Setter Property="Visibility"
Value="{Binding Visibility}" />
<Setter Property="CommandParameter"
Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.Tag.SelectedItems}" />
</Style>
<DataTemplate x:Key="toolBarActionItemTemplate">
<Button Content="{Binding Header}"
Command="{Binding}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ToolBar}, Path=Tag.SelectedItems}"
Visibility="{Binding Visibility}" />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Children>
<ToolBar Grid.Row="0"
ItemsSource="{Binding Actions}"
ItemTemplate="{StaticResource toolBarActionItemTemplate}"
Tag="{Binding}" />
<ListView Grid.Row="1"
ItemsSource="{Binding Items}"
SelectionMode="Extended"
Tag="{Binding}">
<ListView.ContextMenu>
<ContextMenu ItemsSource="{Binding Actions}"
ItemContainerStyle="{StaticResource contextMenuItemStyle}" />
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn Header="Id"
DisplayMemberBinding="{Binding Id}"/>
</GridView>
</ListView.View>
</ListView>
</Grid.Children>
</Grid>
</Window>
CommandBase.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
namespace NullParameter
{
public abstract class CommandBase : ICommand, INotifyPropertyChanged
{
private Visibility _visibility;
private string _error;
public Visibility Visibility
{
get { return _visibility; }
protected set
{
if (_visibility == value)
return;
_visibility = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("Visibility"));
}
}
public string Error
{
get { return _error; }
set
{
if (_error == value)
return;
_error = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("Error"));
}
}
public bool CanExecute(object parameter)
{
Error = DoCanExecute(parameter);
Visibility = DoIsVisible(parameter) ? Visibility.Visible : Visibility.Collapsed;
return Error == null;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
DoExecute(parameter);
}
protected abstract string DoCanExecute(object parameter);
protected abstract bool DoIsVisible(object parameter);
protected abstract void DoExecute(object parameter);
public event PropertyChangedEventHandler PropertyChanged;
}
}
Command.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace NullParameter
{
public class Command : CommandBase
{
public Action OnExecute { get; set; }
public Func<bool> OnIsVisible { get; set; }
public Func<string> OnCanExecute { get; set; }
public string Header { get; set; }
protected override string DoCanExecute(object parameter)
{
if (OnCanExecute == null)
return null;
return OnCanExecute();
}
protected override bool DoIsVisible(object parameter)
{
if (OnIsVisible == null)
return true;
return OnIsVisible();
}
protected override void DoExecute(object parameter)
{
if (OnExecute == null)
return;
OnExecute();
}
}
public class Command<T> : CommandBase
where T : class
{
public Action<T> OnExecute { get; set; }
public Func<T, bool> OnIsVisible { get; set; }
public Func<T, string> OnCanExecute { get; set; }
public string Header { get; set; }
protected T Convert(object parameter)
{
if (parameter == null)
Console.WriteLine("NULL");
return parameter as T;
}
protected override string DoCanExecute(object parameter)
{
if (OnCanExecute == null)
return null;
var p = Convert(parameter);
if (p == null)
return "Invalid Parameter";
return OnCanExecute(p);
}
protected override bool DoIsVisible(object parameter)
{
if (OnIsVisible == null)
return true;
var p = Convert(parameter);
if (p == null)
return false;
return OnIsVisible(p);
}
protected override void DoExecute(object parameter)
{
if (OnExecute == null)
return;
var p = Convert(parameter);
if (p == null)
return;
OnExecute(p);
}
}
}
ListViewModel.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace NullParameter
{
public class ListViewModel
{
public IList<ViewModel> Items { get; private set; }
public IList SelectedItems { get; private set; }
public IList<ICommand> Actions { get; private set; }
public ListViewModel()
{
var items = new ObservableCollection<ViewModel>()
{
new ViewModel()
{
Id = 1
},
new ViewModel()
{
Id = 2
},
new ViewModel()
{
Id = 3
},
};
Items = items;
SelectedItems = items;
Actions = new List<ICommand>()
{
new Command()
{
OnExecute = ShowAlways,
Header = "Show Always"
},
new Command<IList<ViewModel>>()
{
OnExecute = ShowWhenSelected,
OnIsVisible = (list) => { return list.Count(o => o.IsSelected) > 0; },
Header = "Show When Selected"
}
};
}
public void ShowAlways()
{
Console.WriteLine("ShowAlways()");
}
public void ShowWhenSelected(IList<ViewModel> viewModels)
{
Console.WriteLine("ShowWhenSelected({0})", String.Join(",", viewModels.Where(o => o.IsSelected).Select(o => o.Id)));
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NullParameter
{
public class ViewModel : INotifyPropertyChanged
{
private bool _isSelected;
private int _id;
public event PropertyChangedEventHandler PropertyChanged;
public int Id
{
get { return _id; }
set
{
if (_id == value)
return;
_id = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("Id"));
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected == value)
return;
_isSelected = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
}
}

So after reading a few posts about how glitchy the CommandParameter DependencyProperty is, I've given up on using it entirely. Instead, I simply construct my Command objects by passing in the list of selected items in my ListViewModel. Then in the CanExecute and Execute method, I use the stored list of selected items instead of the .NET supplied parameter.
Although this provides a workable solution, it does not necessarily solve the problem posed by the initial question. So I will leave this here as a suggestion for anyone else unfortunate enough to have these issues.

Related

How to add a countdown timer to a child element in Wpf

Im quite new to coding. So far I have a WPF application that when I press submit it creates the treeview but I wanted to add a countdown timer for each child item and have it display the time remaining next to the child item. The problem is the treeview doesn't update and I dont know how to assign a timer for each child item
using Microsoft.Azure.Cosmos.Core.Collections;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace Test_v2
{
public partial class MainWindow : Window
{
public int secondsCount = 100;
public MainWindow()
{
InitializeComponent();
DispatcherTimer disTmr = new DispatcherTimer();
disTmr.Tick += new EventHandler(disTmr_Tick);
disTmr.Interval = new TimeSpan(0, 0, 1);
disTmr.Start();
}
public void disTmr_Tick(object sender, EventArgs e)
{
secondsCount--;
}
List<TreeViewItem> folderList = new List<TreeViewItem>();
public void SubmitButton_Click(object sender, RoutedEventArgs e)
{
if (Folder.Text.Length == 0)
{
ErrorBlock.Text = "Please Enter Folder Name";
return;
}
if (Name.Text.Length == 0)
{
ErrorBlock.Text = "Please Enter a Name";
return;
}
TreeViewItem parent = new TreeViewItem();
for (int i = 0; i < folderList.Count; i++)
{
if (folderList[i].Header.ToString() == Folder.Text)
{
parent = folderList[i];
break;
}
}
if (folderList.Contains(parent))
{
FolderInArrayBlock.Text = "True";
TreeViewItem newChild = new TreeViewItem();
newChild.Header = Name.Text + secondsCount.ToString();
parent.Items.Add(newChild);
}
else
{
FolderInArrayBlock.Text = "false";
TreeViewItem? treeItem = null;
treeItem = new TreeViewItem();
treeItem.Header = Folder.Text;
folderList.Add(treeItem);
treeItem.Items.Add(new TreeViewItem() { Header = Name.Text + secondsCount.ToString()});
LearningItems.Items.Add(treeItem);
}
}
}
}
First of all, if you are using Wpf, you need to use MVVM approch if you want to make a sustainable and maintainable code. This means you need to seperate View funcionalities from Model funcionalities and use a ViewModel as a bridge to be able to communicate with those two things. In Wpf we should try to use Bindings and notifypropertychange to build the brige between View and ViewModel and not use control naming for later use in code behind .cs.(Code behind is the .cs file which belongs to .xaml file ex.: MainWindow.xaml.cs)
I recommend you to take a look at this page, which explains why its so important to use MVVM in your Wpf applications: MVVM pattern
I have created a sample project which demonstrate a proper approch for your task, in my opinion.
MainWindow.xaml
<Window x:Class="TreeViewWithCountDown.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TreeViewWithCountDown"
xmlns:localviewmodels="clr-namespace:TreeViewWithCountDown.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TreeView ItemsSource="{Binding Path=Items, Mode=OneWay}">
<!--We use TreeView Resources because we bind Items as ItemSource and Items is a List of StorageItems, which can be either FolderItem or FileItem.
TreeView can display the two types differently if we specify in the Resources-->
<TreeView.Resources>
<!--Here we specify how to display a FolderItem-->
<HierarchicalDataTemplate DataType="{x:Type localviewmodels:FolderItem}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}"
Margin="0 0 35 0"/>
</HierarchicalDataTemplate>
<!--Here we specify how to display a FileItem-->
<DataTemplate DataType="{x:Type localviewmodels:FileItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="FileNames"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Name}"
Margin="0 0 35 0"
Grid.Column="0"/>
<TextBlock Text="{Binding Path=CountdownTime}"
Margin="0 0 15 0"
Grid.Column="1">
</TextBlock>
</Grid>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
MainWindow.xaml.cs
using System.Windows;
namespace TreeViewWithCountDown
{
public partial class MainWindow : Window
{
private ViewModel _viewModel= new ViewModel();
public MainWindow()
{
InitializeComponent();
//Really important to define where to look for the binding properties
DataContext = _viewModel;
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Timers;
using TreeViewWithCountDown.ViewModels;
namespace TreeViewWithCountDown
{
public class ViewModel : INotifyPropertyChanged
{
private List<StorageItem> _items = new List<StorageItem>();
public List<StorageItem> Items
{
get => _items;
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged();
}
}
}
public ViewModel()
{
//Filling up our Items property which will be given to the View for display
Random random = new Random();
FileItem item0 = new FileItem("file0", random.Next(0,100));
FolderItem item1 = new FolderItem("folder1");
item1.Items.Add(item0);
FileItem item2 = new FileItem("file2", random.Next(0, 100));
FileItem item3 = new FileItem("file3", random.Next(0, 100));
Timer timer = new Timer(3000);
timer.Elapsed += Time_Elapsed;
timer.Start();
Items.Add(item1);
Items.Add(item2);
Items.Add(item3);
}
private void Time_Elapsed(object sender, ElapsedEventArgs e)
{
foreach (StorageItem item in Items)
{
if (item is FileItem fileItem)
{
fileItem.CountdownTime--;
}
else
{
//Reducing counters of Files in Folders
ReduceFileCountInFolders(item);
}
}
}
//A file can be nested in multiple folders so we can solve this with a recursive method
private void ReduceFileCountInFolders(StorageItem item)
{
if (item is FileItem fileItem)
{
fileItem.CountdownTime--;
}
else if (item is FolderItem folderItem)
{
if (folderItem.Items != null && folderItem.Items.Count > 0)
{
foreach (StorageItem storageItem in folderItem.Items)
{
ReduceFileCountInFolders(storageItem);
}
}
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
try
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
throw new Exception($"PropertyChanged event handler FAILED : {ex.Message}");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
StorageItem.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace TreeViewWithCountDown.ViewModels
{
public class StorageItem : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
public StorageItem(string name)
{
Name = name;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
try
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
throw new Exception($"PropertyChanged event handler FAILED : {ex.Message}");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
FileItem.cs
namespace TreeViewWithCountDown.ViewModels
{
public class FileItem : StorageItem
{
private int _countdownTime;
public int CountdownTime
{
get => _countdownTime;
set
{
if (_countdownTime != value && value > 0)
{
_countdownTime = value;
OnPropertyChanged();
}
}
}
public FileItem(string name, int num) : base(name)
{
CountdownTime = num;
}
}
}
FolderItem.cs
using System.Collections.Generic;
namespace TreeViewWithCountDown.ViewModels
{
public class FolderItem : StorageItem
{
private List<StorageItem> _items = new List<StorageItem>();
public List<StorageItem> Items
{
get => _items;
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged();
}
}
}
public FolderItem(string name) : base(name)
{
}
}
}
The final look: View
Hope this will help, if anything seems complicated, feel free to ask!

Change WPF DataGrid column cell background color based on values

I am trying to fill cells with color in certain column. Column name is "NRO" and I want to fill cells staring with 2 yellow color and cells starting with 3 blue color. I went through answer given here: Change DataGrid cell colour based on values
Also tried several other approaches but can't get any of them work. I also don't understand how to implement any of them in my setup. They all seems to have <DataGridTextColumn Binding="{Binding Name}">. What it should be in my case?
Here is my XAML:
<Window x:Class="DB_inspector_FilterTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="DB database inspector v.0.0.01" Height="600" Width="1000" Icon="logo_icon-small.jpg" Background="White">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid x:Name="DataGrid1" Margin="0,103,0,0" Background="White" BorderBrush="#FF38853F"/>
<TextBox x:Name="NameSearch" HorizontalAlignment="Left" Height="20" Margin="22,41,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="437" TextChanged="NameSearch_TextChanged"/>
<Button Content="Load" Margin="640,41,0,0" Click="Button_Click_1" BorderBrush="{x:Null}" Foreground="White" Background="#FF55B432" Width="66" Height="29" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ProgressBar x:Name="ProgressBar" HorizontalAlignment="Left" Height="11" VerticalAlignment="Top" Width="992" BorderBrush="{x:Null}" Background="{x:Null}"/>
<Label Content="Customer name" HorizontalAlignment="Left" Height="25" Margin="22,11,0,0" VerticalAlignment="Top" Width="154"/>
<CheckBox x:Name="ActiveCustomer" Content="Active" HorizontalAlignment="Left" Height="24" Margin="486,63,0,0" VerticalAlignment="Top" Width="86" Click="ActiveCustomer_Click_1"/>
<CheckBox x:Name="Only" Content="Leave only good" HorizontalAlignment="Left" Height="17" Margin="486,41,0,0" VerticalAlignment="Top" Width="115" Click="CheckBox_Click"/>
<Image Margin="856,0,22,520" VerticalAlignment="Bottom" Source="logo_small.jpg" Height="27"/>
</Grid>
</Window>
ADDITION:
If anybody have time, while I will be trying to figure it out myself, give me some hints how to proceed with my application, here is my full code:
using System.Data.Odbc;
using System.Windows;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
namespace DB_inspector_FilterTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
try
{
ProgressBar.IsIndeterminate = true;
DataGrid1.ItemsSource = await GetDataAsync();
ProgressBar.IsIndeterminate = false;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private Task<DataView> GetDataAsync()
{
return Task.Run(() =>
{
string connectionStringDE = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=#DBFSSE;Uid=ADMIN;Pwd=123;";
string queryStringDE = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";
string connectionStringFR = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=#DBFSFI;Uid=ADMIN;Pwd=123;";
string queryStringFR = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";
DataTable dataTable = new DataTable("COMPANY");
// using-statement will cleanly close and dispose unmanaged resources i.e. IDisposable instances
using (OdbcConnection dbConnectionDE = new OdbcConnection(connectionStringDE))
{
dbConnectionDE.Open();
OdbcDataAdapter dadapterDE = new OdbcDataAdapter();
dadapterDE.SelectCommand = new OdbcCommand(queryStringDE, dbConnectionDE);
dadapterDE.Fill(dataTable);
}
using (OdbcConnection dbConnectionFR = new OdbcConnection(connectionStringFR))
{
dbConnectionFR.Open();
OdbcDataAdapter dadapterFR = new OdbcDataAdapter();
dadapterFR.SelectCommand = new OdbcCommand(queryStringFR, dbConnectionFR);
var newTable = new DataTable("COMPANY");
dadapterFR.Fill(newTable);
dataTable.Merge(newTable);
}
return dataTable.DefaultView;
});
}
private Dictionary<string, string> _conditions = new Dictionary<string, string>();
private void UpdateFilter()
{
try
{
var activeConditions = _conditions.Where(c => c.Value != null).Select(c => "(" + c.Value + ")");
DataView dv = DataGrid1.ItemsSource as DataView;
dv.RowFilter = string.Join(" AND ", activeConditions);
}
catch (Exception)
{
//MessageBox.Show(ex.Message);
}
}
private void NameSearch_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
string filter = NameSearch.Text;
if (string.IsNullOrEmpty(filter))
_conditions["name"] = null;
else
_conditions["name"] = string.Format("NAME Like '%{0}%'", filter);
UpdateFilter();
}
private void ActiveCustomer_Click_1(object sender, RoutedEventArgs e)
{
if (ActiveCustomer.IsChecked == true)
{
_conditions["active"] = string.Format("ACTIVE Like '%{0}%'", "1");
UpdateFilter();
}
else
{
_conditions["active"] = null;
UpdateFilter();
}
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
if (OnlyFIandSE.IsChecked == true)
{
_conditions["onlyfrandde"] = string.Format("NRO Like '2%' OR NRO Like '3%'");
UpdateFilter();
}
else
{
_conditions["onlyfrandde"] = null;
UpdateFilter();
}
}
}
}
Things I don't understand at least now: How in my case I should setup ItemSource for binding? Should I import databases to List first and then Bind to the list?
ATTEMPT 3:
Here is my latest MVVM attempt.
C#:
using System;
using System.ComponentModel;
using System.Data;
using System.Data.Odbc;
using System.Windows;
using System.Windows.Input;
namespace DB_inspector
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
}
public class ViewModel : INotifyPropertyChanged
{
public ICommand myCommand => new RelayCommand(obj =>
{
try
{
string connectionStringDE = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=#DBFSSE;Uid=ADMIN;Pwd=123;";
string queryStringDE = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";
string connectionStringFR = "Driver={Pervasive ODBC Client Interface};ServerName=DB123;dbq=#DBFSFI;Uid=ADMIN;Pwd=123;";
string queryStringFR = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTB,CORPORATION,COUNTRY,ID,ACTIVE from COMPANY";
DataTable dataTable = new DataTable("COMPANY");
// using-statement will cleanly close and dispose unmanaged resources i.e. IDisposable instances
using (OdbcConnection dbConnectionDE = new OdbcConnection(connectionStringDE))
{
dbConnectionDE.Open();
OdbcDataAdapter dadapterDE = new OdbcDataAdapter();
dadapterDE.SelectCommand = new OdbcCommand(queryStringDE, dbConnectionDE);
dadapterDE.Fill(dataTable);
}
using (OdbcConnection dbConnectionFR = new OdbcConnection(connectionStringFR))
{
dbConnectionFR.Open();
OdbcDataAdapter dadapterFR = new OdbcDataAdapter();
dadapterFR.SelectCommand = new OdbcCommand(queryStringFR, dbConnectionFR);
var newTable = new DataTable("COMPANY");
dadapterFR.Fill(newTable);
dataTable.Merge(newTable);
}
_ = dataTable.DefaultView;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
});
private bool _allowUIChanges = true;
public bool AllowUIChanges
{
get => _allowUIChanges;
set
{
_allowUIChanges = value;
OnPropertyChanged(nameof(AllowUIChanges));
OnPropertyChanged(nameof(IsReadOnlyDataGrid));
}
}
private void OnPropertyChanged(string v)
{
throw new NotImplementedException();
}
public bool IsReadOnlyDataGrid
{
get => !_allowUIChanges;
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
}
XAML:
<Window x:Class="DB_inspector.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="DB database inspector" Height="595.404" Width="1005.571">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<DataGrid IsReadOnly="{Binding IsReadOnlyDataGrid}" AutoGenerateColumns="False" ItemsSource="{Binding MyCollection}" Width="998" Margin="0,98,0,0" >
</DataGrid>
<Image Height="41" Margin="0,21,10,0" Width="141" Source="logo_small.jpg" HorizontalAlignment="Right" VerticalAlignment="Top"/>
<Button Content="Go" Command="{Binding myCommand}" Width="80" Height="30" Margin="48,42,870,492"/>
</Grid>
</Window>
Any suggestions what is still wrong here? No errors, but button does not process anything.
I suggest IValueConverter or IMultiValueConverter binding for Cell's Background property.
I'm not sure how it works with autogenerated columns set but with manual it looks like this. I'm providing here not a working copy but a markup example.
XAML
<Window.Resources>
<local:MyCellBackgroundConverter x:Key="myCellBackgroundConverter"/>
</Window.Resources>
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<!-- some your markup here -->
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyCollection}">
<DataGridTextColumn Header="Column1" Binding="{Binding Value1}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource myCellBackgroundConverter}">
<Binding Path="Value1"/>
<Binding Path="Value2"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Column2" Binding="{Binding Value2}"/>
</DataGrid>
</Grid>
The ViewModel Class
using System.Collections.ObjectModel;
using System.ComponentModel;
// ...
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyItem> _myCollection = new ObservableCollection<MyItem>();
public ObservableCollection<MyItem> MyCollection
{
get => _myCollection;
set
{
_myCollection = value;
OnPropertyChanged(nameof(MyCollection));
}
}
public ViewModel()
// you may load or add the data to MyCollection here
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Item
using System.ComponentModel;
// ...
public class MyItem : INotifyPropertyChanged
{
private string _value1 = string.Empty;
private string _value2 = string.Empty;
public string Value1
{
get => _value1;
set { _value1 = value; OnPropertyChanged(nameof(Value1)); }
}
public string Value2
{
get => _value2;
set { _value2 = value; OnPropertyChanged(nameof(Value2)); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
And finally the Converter
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
//...
public class MyCellBackgroundConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is string value1 && values[1] is string value2)
{
if (value1.Length > 0)
{
return Brushes.Green;
}
else
if (value2.Length > 0)
{
return Brushes.Yellow;
}
else
return Brushes.Red;
}
else return Brushes.White;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => null;
}
In alternative way you may use Style.DataTriggers directly in XAML.
For additional information about bindings and properties look for MVVM programming pattern. In short you're not need x:Name anymore because in MVVM pattern you interacting only with ViewModel class data instances and can't interact with contols directly there (and that's fine). Meanwhile Contols automatically sync with data binded to them. Calling OnPropertyChanged("PropertyName") here simply cause the GUI refresh.
In relation to markup of your XAML example, try wrapping the Control groups in StackPanel and learn about it. It will save your time spent fighting with margins. Simply set few colums and/or rows in Window's Grid and place StackPanels there assigning Grid.Column and Grid.Row to them.
ADDITION:
How in my case I should setup ItemSource for binding? Should I import databases to List first and then Bind to the list?
ObservableCollection<> is same as List<> and you may use it in the same way. The difference that first one implements CollectionChanged event that notifies DataGrid if any items was added or removed from the collection.
Your Button.Click event handler contains redundant async/await declaration.
Let's move forward and see how it can be done with MVVM.
XAML
<Button Content="Go" Command="{Binding myCommand}"/>
Command must implement ICommand interface. You have to do 2 things for proper implementation:
1) Add RelayCommand separate Class implementing ICommand interface
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
2) Add the Command instance to your ViewModel class
public ICommand myCommand => new RelayCommand(obj =>
{
// do the same here as in Button.Click above
});
Next, you may need some blocking UI thing that will prevent user from any actions while data is loading.
ViewModel
private bool _allowUIChanges = true;
public bool AllowUIChanges
{
get => _allowUIChanges;
set
{
_allowUIChanges = value;
OnPropertyChanged(nameof(AllowUIChanges));
OnPropertyChanged(nameof(IsReadOnlyDataGrid));
}
}
public bool IsReadOnlyDataGrid
{
get => !_allowUIChanges;
}
Finally bind your Control properties to it
XAML
<Button Content="Go" Command="{Binding myCommand}" Enabled="{Binding AllowUIChanges}"/>
<DataGrid IsReadOnly="{Binding IsReadOnlyDataGrid}" AutoGenerateColumns="False" ItemsSource="{Binding MyCollection}">
Then set AllowUIChanges to false while data is loading.

How do I filter an ICollectionView based on the sub property of an item?

I'm trying to filter by a sub property of the items (Processor objects) in my collection but am unable. The property is a simple Boolean to indicate if the item is enabled or disabled. Enabled items in the list should be listed in the other listbox found on the first tab (enabled processors).
Below is a full working example that can be pasted in a starter WPF application
If you uncomment this line
_selectedProcessorsView.Filter = processorFilter;
in the MainWindow_VM() constructor, the processors are no longer displayed in the configuration tab.
Two main questions:
Why does the filtering affect both lists?
How can I fix this so that the enabled processors listbox on the first tab is bound to only the checked items in the second tab?
NOTE: The three classes at the end of my code (Config, Settings, and Processor are part of a class library that I can't modify. The WPF app is just a UI around that library.
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindow_VM></local:MainWindow_VM>
</Window.DataContext>
<Grid>
<TabControl>
<TabItem Header="enabled processors">
<ListBox ItemsSource="{Binding selectedProcessorsView}" VerticalAlignment="Top" FontSize="14">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=processorType}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</TabItem>
<TabItem Header="configuration">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView Grid.Column="0" ItemsSource="{Binding Path=reportTypes}" SelectedItem="{Binding selectedConfiguration}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=id, Mode=OneWay}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="1" ItemsSource="{Binding reportProcessors}" SelectedItem="{Binding selectedProcessor}">
<ListView.View>
<GridView>
<GridViewColumn Header="Enabled" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Settings.isEnabled}" ></CheckBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Processor" DisplayMemberBinding="{Binding Path=processorType, Mode=OneWay}" />
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding Path=processorId, Mode=OneWay}" >
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MainWindow_VM : BindableBase
{
public MainWindow_VM()
{
reportTypes.Add(new Config()
{
id = Guid.NewGuid().ToString(),
Processors = new Dictionary<string, Processor>() {
{ "Processor 1", new Processor() { processorType = "Blue" } },
{ "Processor 2", new Processor() { processorType = "Yellow" } }
}
});
reportTypes.Add(new Config()
{
id = Guid.NewGuid().ToString(),
Processors = new Dictionary<string, Processor>() {
{ "Processor 3", new Processor() { processorType = "Green" } },
{ "Processor 4", new Processor() { processorType = "Red" } }
}
});
_selectedProcessorsView = CollectionViewSource.GetDefaultView(reportProcessors);
//_selectedProcessorsView.Filter = processorFilter;
}
public bool processorFilter(object item)
{
bool result = true;
Processor p = item as Processor;
if (p.Settings.isEnabled == false)
result = false;
return result;
}
private Config _selectedConfiguration;
public Config selectedConfiguration
{
get { return _selectedConfiguration; }
set
{
SetProperty(ref _selectedConfiguration, value);
reportProcessors.Clear();
foreach (KeyValuePair<string, Processor> kvp in value.Processors)
reportProcessors.Add(kvp.Value);
}
}
public ObservableCollection<Config> reportTypes { get; } = new ObservableCollection<Config>();
public ObservableCollection<Processor> reportProcessors { get; } = new ObservableCollection<Processor>();
private ICollectionView _selectedProcessorsView;
public ICollectionView selectedProcessorsView
{
get { return _selectedProcessorsView; }
}
}
///These three classes below are part of a separate class library that I cannot modify
public class Config
{
public string id { get; set; }
public Dictionary<string, Processor> Processors { get; set; }
}
public class Processor
{
public string processorType { get; set; }
public Settings Settings { get; set; } = new Settings() {
isEnabled = false
};
}
public class Settings
{
public bool isEnabled { get; set; }
}
}
Why does the filtering affect both lists?
{Binding reportProcessors} and {Binding selectedProcessorsView} bind to the same view. When you bind to an ObservableCollection<T>, or any other type of source collection for that matter, you are actually binding to an automatically generated CollectionView and not to the actual source collection itself.
How can I fix this so that the enabled processors listbox on the first tab is bound to only the checked items in the second tab?
Bind to two different views. But you also need to notify the view when the isEnabled propery is actually set to a new value. Otherwise your filter won't be updated as you check the CheckBoxes. The fact that you are using a Setting class complicates this a bit but you could add a property to the Processor class and live filter on this one. Both Settings and Processor need to implement the INotifyPropertyChanged event.
Please refer to the following sample code. It should give you the idea.
public class MainWindow_VM : BindableBase
{
public MainWindow_VM()
{
...
ListCollectionView selectedProcessorsView = new ListCollectionView(reportProcessors);
selectedProcessorsView.LiveFilteringProperties.Add(nameof(Processor.IsEnabled));
selectedProcessorsView.IsLiveFiltering = true;
selectedProcessorsView.Filter = processorFilter;
_selectedProcessorsView = selectedProcessorsView;
}
...
}
public class Processor : INotifyPropertyChanged
{
public string processorType { get; set; }
public Settings Settings { get; set; } = new Settings()
{
isEnabled = false
};
public bool IsEnabled => Settings.isEnabled;
public Processor()
{
Settings.PropertyChanged += Settings_PropertyChanged;
}
private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(IsEnabled));
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Settings : INotifyPropertyChanged
{
private bool _isEnabled;
public bool isEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
In the end I went with the following approach, combined mm8's answer to my first question, with my own wrapper class that acts as a viewmodel to the Processor object (model).
Whenever a checkbox is checked or unchecked, the listviewcollection is refreshed thus updating the collection displayed in the first tab.
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MainWindow_VM : BindableBase
{
public MainWindow_VM()
{
reportTypes.Add(new Config()
{
id = Guid.NewGuid().ToString(),
Processors = new Dictionary<string, Processor>() {
{ "Processor 1", new Processor() { processorType = "Blue" } },
{ "Processor 2", new Processor() { processorType = "Yellow" } }
}
});
reportTypes.Add(new Config()
{
id = Guid.NewGuid().ToString(),
Processors = new Dictionary<string, Processor>() {
{ "Processor 3", new Processor() { processorType = "Green" } },
{ "Processor 4", new Processor() { processorType = "Red" } }
}
});
selectedProcessorsView = new ListCollectionView(processors);
selectedProcessorsView.Filter = processorFilter;
}
public bool processorFilter(object item)
{
bool result = true;
ProcessorWrapper p = item as ProcessorWrapper;
if (p.isEnabled == false)
result = false;
return result;
}
private Config _selectedConfiguration;
public Config selectedConfiguration
{
get { return _selectedConfiguration; }
set
{
SetProperty(ref _selectedConfiguration, value);
processors.Clear();
foreach (KeyValuePair<string, Processor> kvp in value.Processors)
processors.Add(new ProcessorWrapper(kvp.Value, this.selectedProcessorsView));
}
}
public ObservableCollection<Config> reportTypes { get; } = new ObservableCollection<Config>();
public ObservableCollection<ProcessorWrapper> processors { get; } = new ObservableCollection<ProcessorWrapper>();
public ListCollectionView selectedProcessorsView { get; set; }
}
public class ProcessorWrapper : BindableBase
{
public ProcessorWrapper(Processor processor, ListCollectionView lcv)
{
_processor = processor;
_lcv = lcv;
}
private Processor _processor;
private ListCollectionView _lcv;
private string _processorType;
public string processorType
{
get {
_processorType = _processor.processorType;
return _processorType;
}
}
private bool _isEnabled;
public bool isEnabled
{
get {
_isEnabled = this._processor.Settings.isEnabled;
return _isEnabled; }
set
{
SetProperty(ref _isEnabled, value);
this._processor.Settings.isEnabled = _isEnabled;
_lcv.Refresh();
}
}
}
/// <summary>
/// These classes belong to a separate class library, should not be modified.
/// </summary>
public class Config
{
public string id { get; set; }
public Dictionary<string, Processor> Processors { get; set; }
}
public class Processor
{
public string processorType { get; set; }
public Settings Settings { get; set; } = new Settings() {
isEnabled = false
};
}
public class Settings
{
public bool isEnabled { get; set; }
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindow_VM></local:MainWindow_VM>
</Window.DataContext>
<Grid>
<TabControl>
<TabItem Header="enabled processors">
<ListBox ItemsSource="{Binding selectedProcessorsView}" VerticalAlignment="Top" FontSize="14">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=processorType}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</TabItem>
<TabItem Header="configuration">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView Grid.Column="0" ItemsSource="{Binding Path=reportTypes}" SelectedItem="{Binding selectedConfiguration}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=id, Mode=OneWay}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="1" ItemsSource="{Binding processors}" SelectedItem="{Binding selectedProcessor}">
<ListView.View>
<GridView>
<GridViewColumn Header="Enabled" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=isEnabled}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Processor" DisplayMemberBinding="{Binding Path=processorType, Mode=OneWay}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>

SelectedValue binding does not write write back

I'm new to wpf. Trying to add a listbox in a datagrid. Everything runs perfect but the selected value binding is not working. It's not writing the SelectedValue back. Please help.
<DataGrid Name="SoruDataGrid" ItemsSource="{Binding Test.TestSonucuCollection}" Grid.Row="1" AutoGenerateColumns="False" Grid.ColumnSpan="2" >
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Soru.Id}"/>
<DataGridTextColumn Header="Soru" Binding="{Binding Soru.Text}" Width="300">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="AcceptsReturn" Value="true" />
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Header="Cevap">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ListBox SelectionMode="Single"
ItemsSource="{Binding Soru.CevapCollection}"
DisplayMemberPath="Text"
SelectedValuePath="{Binding Id}"
SelectedValue="{Binding CevapId, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
CevapModel class
using System.ComponentModel;
namespace Test.Model
{
public class CevapModel : INotifyPropertyChanged
{
Cevap _cevap;
public event PropertyChangedEventHandler PropertyChanged;
public CevapModel()
{
_cevap = new Cevap();
}
public CevapModel(Cevap cevap)
{
_cevap = cevap;
}
public int Id
{
get { return _cevap.Id; }
set
{
_cevap.Id = value;
OnPropertyChanged("Id");
}
}
public int SoruId
{
get { return _cevap.SoruId; }
set
{
_cevap.SoruId = value;
OnPropertyChanged("SoruId");
}
}
public string Text
{
get { return _cevap.Text; }
set
{
_cevap.Text = value;
OnPropertyChanged("Text");
}
}
public int Puan
{
get { return _cevap.Puan; }
set
{
_cevap.Puan = value;
OnPropertyChanged("Puan");
}
}
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
TestSonucuModel.cs
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace Test.Model
{
public class TestSonucuModel : INotifyPropertyChanged
{
TestSonucu _testSonucu;
SoruModel _soru;
public event PropertyChangedEventHandler PropertyChanged;
public TestSonucuModel()
{
_testSonucu = new TestSonucu();
}
public TestSonucuModel(TestSonucu testSonucu)
{
_testSonucu = testSonucu;
}
public int Id
{
get { return _testSonucu.Id; }
set
{
_testSonucu.Id = value;
OnPropertyChanged("Id");
}
}
public int TestId
{
get { return _testSonucu.TestId; }
set
{
_testSonucu.TestId = value;
OnPropertyChanged("TestId");
}
}
public int SoruId
{
get { return _testSonucu.SoruId; }
set
{
_testSonucu.SoruId = value;
OnPropertyChanged("SoruId");
}
}
public SoruModel Soru
{
get { return _soru; }
set
{
_soru = value;
OnPropertyChanged("Soru");
}
}
public int CevapId
{
get { return _testSonucu.CevapId; }
set
{
_testSonucu.CevapId = value;
OnPropertyChanged("CevapId");
}
}
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public static implicit operator TestSonucu(TestSonucuModel t)
{
return t._testSonucu;
}
}
}}
If, as you said, CevapId is a property of TestSonucu class which is the view model behind each row of your DataGrid you don't need to change binding context in this case as it will be set to instance of TestSonucu class
<ListBox ...
ItemsSource="{Binding Soru.CevapCollection}"
DisplayMemberPath="Text"
SelectedValuePath="Id"
SelectedValue="{Binding Path=CevapId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
also SelectedValuePathshould be just Id, in the same way as you specify DisplayMemberPath which means it will take Text property of each CevapCollection item for display and Id property as value

Listivew with binding not updating

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

Categories