I am working on a wpf application and I am dealing with checkboxes now
The problem when I set the Ischecked property to "True" like this:
<CheckBox Visibility="{Binding checkVisibility}" IsChecked="true"
IsEnabled="True"></CheckBox>
I get my checkbox checked
But when I try to bind a booléan property that it's value is "true" i get my checkbox always unchecked
I can't see where is the proplem
this is my xaml
<CheckBox Visibility="{Binding checkVisibility}" IsChecked="{Binding Path=test,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" IsEnabled="True"></CheckBox>
this is my property
public bool _test = true;
public bool test {
get {
return _test;
}
set {
if (_test == value) {
return;
}
_test = value;
RaisePropertyChanged("test");
}
}
I want my checkbox to reflect my property value and vice versa but it's not the case as my checkbox is always unchecked
I am missing something?
Edit
Here is my VM:
namespace X{
public class MyViewModel :
{
public MyViewModel()
{
TestCheckbox();
}
#Region Properties
public bool _test1 = true;
public bool test1
{
get
{
return _test1;
}
set
{
if (_test1 == value)
{
return;
}
_test1 = value;
RaisePropertyChanged("test1");
}
}
ObservableCollection<Output> _output_List;
public ObservableCollection<Output> output_List
{
get { return _output_List; }
set
{
if (_output_List == value) return;
_output_List = value;
RaisePropertyChanged("output_List");
}
}
#EndRegion
#Region ButtonCommand
private RelayCommand _SetOutputPropertiesCommand;
public RelayCommand SetOutputPropertiesCommand => _SetOutputPropertiesCommand
?? (_SetOutputPropertiesCommand = new RelayCommand(
() =>
{
foreach (Output item in output_List)
{
item.myvalue=Test2;
}
}
}));
#EndRegion
#Region Method
public void TestCheckbox()
{
Console.Writeline(Test2);
}
#EndRegion
}
public class Output
{
public string label { get; set; }
public string type { get; set; }
public bool myvalue { get; set; }
public bool Test2 { get; set; }
[System.ComponentModel.DataAnnotations.Schema.NotMapped]
[JsonIgnore]
public string checkVisibility { get; set; }
}
}
My Xaml : my checkboxes are integrated in a DataGrid view
<DataGrid x:Name ="GridO" Style="{x:Null}"
ItemsSource= "{Binding output_List,UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="False" CellStyle="{StaticResource Body_Content_DataGrid_Centering}"
Margin="5,0" IsReadOnly="True" SelectionMode="Single" RowHeight="50" Height="Auto">
<DataGrid.Columns>
<DataGridTextColumn Width="40*" Binding="{Binding label}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Input"></TextBlock>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
<DataGridTextColumn Width="40*" Binding="{Binding type}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text = "Type"></TextBlock>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
<DataGridTemplateColumn Width="20*" >
<DataGridTemplateColumn.Header>
<TextBlock Text="Value" />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Margin="20,0,0,0" Visibility="{Binding checkVisibility }" IsChecked="{Binding Test2,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" IsEnabled="True"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button x:Name="Valid_output" Cursor="Hand" Background="Transparent" Height="55" Width="140" Margin="0,0,20,0" Command="{Binding SetOutputPropertiesCommand}" >
The bind is Working with "Test2" and not working with "Test1" which not in the same class as Test2
NB: the Button command(which is in the same class as "Test1") is working well
My DataTemplate: are in the App.xaml
xmlns:v="clr-namespace:X.View"
xmlns:vm="clr-namespace:X"
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<v:MyWindow/>
I see the main problem in the DataGrid. You set ItemsSource to a collection, so for every row in the DataGrid the data source is one item in that collection. Not your MyViewModel.
This is the reason, why the Test2 is working - it is in the class which instances are in the collection.
You could try to add CheckBox to the window without DataGrid and bind the Test1 - it should work.
If you really want to use some property from your MyViewModel, you can, but it is not that easy. You have to have there something like:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNamespace"
<!--...something...-->
<DataGrid.Resources>
<local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</DataGrid.Resources>
<!--...something...-->
And then your binding in the DataGrid will look like this:
IsChecked="{Binding Data.Test1, Source={StaticResource Proxy}}"
Of course I don't know the exact settings of your application so you will have to complete/change the code according to them.
I suggest you to add a ViewModelBase class, like that
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName =
null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Extends your ViewModel with this ViewModelBase class and implements your property
private bool myProperty;
public bool MyProperty { get; set; OnPropertyChanged(); }
Then you just have to bind on your property
<CheckBox IsChecked="{Binding MyProperty}"></CheckBox>
EDIT: To set your ViewModel as DataContext for your View you can set it from the code
MyView.DataContext = new MyViewModel();
or from the view, in the Window/User Control
<Window x:Class="MyApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApplication"
Title="MainWindow" Height="350" Width="525"
DataContext="local.MyViewModel">
Related
I was struggling to trigger checked function in WPF, but when it comes to code-behind concept it is okay, but I want to get checked button value with MVVM theory. So do you guys have any idea about this? and in the below, there is tried code with code behind theory.
<DataTemplate>
<dc:RadioButton GroupName="DemoRadios1"
Margin="0,0,15,0"
IsEnabled="{Binding RadioIsEnabled}"
IsReadOnly="{Binding RadioIsReadOnly}"
InnerCheckerVisibilityWhenReadOnly="{Binding RadioInnerCheckerVisibilityWhenReadOnly}"
InnerCheckerVerticalAlignment="{Binding RadioInnerCheckerVerticalAlignment}"
IsChecked="{Binding IsChecked}"
Content="{Binding OverridedSettingValueName}"
Checked="RadioButton_Checked"/>
</DataTemplate>
#Code behind function
private void RadioButton_Checked(object sender, RoutedEventArgs e)
{
// ... Get RadioButton reference.
var button = sender as RadioButton;
// ... Display button content as title.
var Title = button.Content.ToString();
}
It looks like maybe you have a collection of things you're working with.
RadioButton has a command property, so you could potentially bind a command and commandparameter if this code must be in the window viewmodel.
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<StackPanel>
<TextBlock Text="{Binding RowName}"/>
<ItemsControl ItemsSource="{Binding Rows}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Name}"
IsChecked="{Binding IsChecked}"
Command="{Binding DataContext.CheckedCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
GroupName="DemoRadios1"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
My viewmodel:
public class MainWindowViewModel : BindableBase
{
public DelegateCommand<RowVM> CheckedCommand { get; private set; }
public ObservableCollection<RowVM> Rows{ get; set; } = new ObservableCollection<RowVM>(
new List<RowVM>
{
new RowVM{Name="Andrew"},
new RowVM{Name="Bill"},
new RowVM{Name="Carol"},
}
);
private string rowName;
public string RowName
{
get => rowName;
set { SetProperty(ref rowName, value); }
}
public MainWindowViewModel()
{
CheckedCommand = new DelegateCommand<RowVM>((row) =>
{
if(!row.IsChecked)
{
return;
}
RowName = row.Name;
});
}
My RowVM lacks any raising of property changed.
public class RowVM : BindableBase
{
public string Name { get; set; }
public bool IsChecked { get; set; } = false;
}
I'm guessing this datatemplate is used for a collection so there will be an itemscontrol with an itemssource bound to a collection of row viewmodels.
This is somewhat simpler if you can instead have code in the viewmodel for a row.
You already bound IsChecked.
You can therefore just put some code in the set accessor if that bound IsChecked property of your viewmodel:
public class RowViewModel : BindableBase
{
private bool isChecked;
public bool IsChecked
{
get => isChecked;
set
{
isChecked = value;
DoStuff(value);
RaisePropertyChanged();
}
}
I'm trying to bind a DataGrid with an ObservableCollection list. I basically add a few items to the list (which should add rows to the datagrid) at the start of the form then update the columns dynamically using this code in a different thread:
UserDataGrid.Dispatcher.BeginInvoke(new Action(() =>
{
UserDataGridCollection[m_iID].Status = Log;
UserDataGridCollection[m_iID].ID = m_iID;
UserDataGridCollection[m_iID].User = m_sUser;
}
If I update the DataGrid this way it works but lags the UI thread:
UserDataGrid.ItemsSource = null;
UserDataGrid.ItemsSource = UserDataGridCollection;
I've tried using the PropertyChanged event but the DataGrid isn't populating in the first place so I'm not sure if that's working properly. Here is my data class:
public class UserDataGridCategory : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int id;
private string user, status;
public int ID
{
get { return id; }
set { id = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ID")); }
}
public string User
{
get { return user; }
set { user = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("User")); }
}
public string Status
{
get { return status; }
set { status = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Status")); }
}
}
Here is how i'm creating my ObservableCollection:
static class UserEngine
{
public static ObservableCollection<UserDataGridCategory> UserDataGridCollection { get; set; }
public static object _lock = new object();
public static void RunEngine(DataGrid UserDataGrid)
{
UserDataGridCollection = new ObservableCollection<UserDataGridCategory>();
BindingOperations.EnableCollectionSynchronization(UserDataGridCollection, _lock);
// Some other code
// Spawn thread to invoke dispatcher and do some other stuff
}
}
And here is my xaml:
<DataGrid Name="UserDataGrid" x:FieldModifier="public" ItemsSource="{Binding UserDataGridCollection}" SelectedItem="{Binding SelectedRow, Mode=TwoWay}" Margin="10,16,22.6,215" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Resources>
<ContextMenu x:Key="RowMenu" Focusable="False"
DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="View Log" Click="ViewLog" Focusable="False"/>
</ContextMenu>
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" >
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="ID #" Binding="{Binding ID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True"/>
<DataGridTextColumn Header="User" Binding="{Binding User, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True" Width="150"/>
<DataGridTextColumn Header="Status" Binding="{Binding Status,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True" Width="250"/>
</DataGrid.Columns>
</DataGrid>
Any help would be greatly appreciated, thanks!
maybe it helps to bind your itemsource not to an observable collection but to a CollectionViewSource.
public class UserEngine {
public ICollectionView UserdataCollectionView { get; set; }
public UserEngine() { 'constructor
UserdataCollectionView = CollectionViewSource.GetDefaultView(UserDataGridCollection );
}
public void addnewLinetoOC(){
// after you add a new entry into your observable collection
UserdataCollectionView .Refresh();
}
}
in your xaml you have to set
ItemsSource="{Binding UserdataCollectionView }"
not 100% sure if this solution fits your problem but that's how I solved adding new items to my observable collection in my viewmodel.
I got checkbox column in DataGrid that populating realtime download percentage in particular row, filtered by caseRefNo. i need to add checkbox changed event to perform some action. i used InvokeCommandAction to add the action to checkbox.
I realize that when i click the checkbox for the first time, It is ok and trigger only one time. but there are two times triggered when i click second time on the same checkbox.For third time clicking the same checkbox, it triggered four time. quite scary and difficult to figure it out.
here is my viewmodel code
public class DataGridDownloadViewModel:BindableBase
{
public ObservableCollection<tblTransaction> TransList { get; private set; }
public DispatcherTimer dispatchTimer = new DispatcherTimer();
public CollectionView TransView { get; private set; }
public DelegateCommand<object> CheckCommand { get; set; }
private String _UpdatePer;
public String UpdatePercentage
{
get { return _UpdatePer; }
set { SetProperty(ref _UpdatePer, value); }
}
private string _caseId;
public string CaseID
{
get { return _caseId; }
set { SetProperty(ref _caseId, value); }
}
private string _isChecked;
public string isChecked
{
get { return _isChecked; }
set { SetProperty(ref _isChecked, value); }
}
private bool CanExecute(object args)
{
return true;
}
private void CheckBoxChecker(object args)
{
//Should Work Here
// Totally not coming to this function
CheckBox chk = (CheckBox)args;
string thichintae = chk.Name.ToString();
Console.WriteLine(thichintae);
}
public DataGridDownloadViewModel(List<tblTransaction> model)
{
CheckCommand = new DelegateCommand<object>(CheckBoxChecker, CanExecute);
dispatchTimer.Interval = TimeSpan.FromMilliseconds(3000);
dispatchTimer.Tick += dispatchTimer_Tick;
BackGroundThread bgT = Application.Current.Resources["BackGroundThread"] as BackGroundThread;
bgT.GetPercentChanged += (ss, ee) =>
{
UpdatePercentage = bgT.local_percentage.ToString();
};
bgT.GetCaseID += (ss, ee) =>
{
CaseID = bgT.local_caseRef;
};
TransList =new ObservableCollection<tblTransaction>(model);
TransView = GetTransCollectionView(TransList);
TransView.Filter = OnFilterTrans;
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var cancellationTokenSource = new CancellationTokenSource();
dispatchTimer.Start();
}
private void dispatchTimer_Tick(object sender, EventArgs e)
{
UpdateDataGrid();
}
public void UpdateDataGrid()
{
foreach (tblTransaction tran in TransList)
{
if (tran.caseRefNo == CaseID)
{
tran.incValue = int.Parse(UpdatePercentage);
}
else
{
tran.incValue = tran.incValue;
}
}
TransView.Refresh();
}
bool OnFilterTrans(object item)
{
var trans = (tblTransaction)item;
return true;
}
public CollectionView GetTransCollectionView(ObservableCollection<tblTransaction> tranList)
{
return (CollectionView)CollectionViewSource.GetDefaultView(tranList);
}
}
this is XAML for above view model.
<Window x:Class="EmployeeManager.View.DataGridDownload"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Title="DataGridDownload" Height="600" Width="790">
<Grid>
<DataGrid HorizontalAlignment="Left" ItemsSource="{Binding TransView}" AutoGenerateColumns="False" Margin="10,62,0,0" VerticalAlignment="Top" Height="497" Width="762">
<DataGrid.Columns>
<DataGridTextColumn Header="caseRefNo" Binding="{Binding caseRefNo}" />
<DataGridTextColumn Header="subjMatr" Binding="{Binding subjMatr}" />
<DataGridTextColumn Header="Download %" Binding="{Binding incValue}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="abcdef"
Content="Please Select" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction CommandParameter="{Binding ElementName=abcdef}" Command="{Binding DataContext.CheckCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding incValue,UpdateSourceTrigger=PropertyChanged}" Background="Red" Foreground="White" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Label Content="{Binding UpdatePercentage}" HorizontalAlignment="Left" Background="Blue" Foreground="White" Margin="10,10,0,0" VerticalAlignment="Top" Width="338" Height="30">
</Label>
<Button Content="Button" HorizontalAlignment="Left" Margin="672,20,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
Here is my model
public class tblTransaction
{
public string caseRefNo { get;set;}
public string subjMatr { get; set; }
public int incValue { get; set; }
public DateTime? longTime { get; set; }
public bool IsSelected { get; set; }
}
This is picture of my form
Is it because of DispatcherTimer ? All suggestion are welcome.
df
I think I left a comment in your previous question saying that wrapping your collection to CollectionView is quite smelly.
Anyway, the TransView.Refresh(); is causing the problem in your code.
TransView.Refresh will trigger the "Checked" event for each checked checkbox. The refresh basically asking the wpf engine re-populate all the data to your CollectionView and in turn, each checked checkbox will fire the checked event all over again.
Try setting dispatchTimer.Interval to a much shorter time e.g. 300. You should be able to see the "tick" in checkbox keep flicking becoz of the TransView.Refresh.
For real, I have no idea why you don't just bind your TransList to your DataGrid and called BeginInvoke on UpdateDataGrid method.
To demo how ObservableCollection works
I used the ObservableCollection to re-wrote some part of your code. At least make things better fall in line with MVVM model.
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MainViewModel : ViewModelBase
{
public ObservableCollection<TblTransaction> TransList { get; private set; }
public DispatcherTimer DispatchTimer = new DispatcherTimer();
public MainViewModel()
{
var model = new ObservableCollection<TblTransaction>();
for (int i = 0; i < 5; i++)
{
model.Add(new TblTransaction { CaseRefNo = i.ToString(), IncValue = i, LongTime = DateTime.Now, SubjMatr = i.ToString() });
if (i == 3)
model[i].IsSelected = true;
}
DispatchTimer.Interval = TimeSpan.FromMilliseconds(200);
DispatchTimer.Tick += dispatchTimer_Tick;
TransList = model;
DispatchTimer.Start();
}
private void dispatchTimer_Tick(object sender, EventArgs e)
{
UpdateDataGrid();
}
public void UpdateDataGrid()
{
var ran = new Random();
foreach (var tran in TransList)
tran.IncValue = ran.Next(0, 100);
}
}
public class TblTransaction : ViewModelBase
{
private string caseRefNo;
private string subjMatr;
private int incValue;
private DateTime? longTime;
private bool isSelected;
public DelegateCommand<object> CheckCommand { get; set; }
public TblTransaction()
{
CheckCommand = new DelegateCommand<object>(CheckBoxChecker, (p) => true);
}
private void CheckBoxChecker(object args)
{
//Should Work Here
// Totally not coming to this function
//CheckBox chk = (CheckBox)args;
//string thichintae = chk.Name;
Console.WriteLine(args);
}
public string CaseRefNo
{
get { return caseRefNo; }
set
{
caseRefNo = value;
OnPropertyChanged();
}
}
public string SubjMatr
{
get { return subjMatr; }
set
{
subjMatr = value;
OnPropertyChanged();
}
}
public int IncValue
{
get { return incValue; }
set
{
incValue = value;
OnPropertyChanged();
}
}
public DateTime? LongTime
{
get { return longTime; }
set
{
longTime = value;
OnPropertyChanged();
}
}
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
OnPropertyChanged();
}
}
}
<Window x:Class="WpfTestProj.MainWindow"
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"
xmlns:local="clr-namespace:WpfTestProj"
xmlns:interact="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=False}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid HorizontalAlignment="Left" ItemsSource="{Binding TransList}" AutoGenerateColumns="False" Margin="10,62,0,0" VerticalAlignment="Top" Height="497" Width="762">
<DataGrid.Columns>
<DataGridTextColumn Header="caseRefNo" Binding="{Binding CaseRefNo}" />
<DataGridTextColumn Header="subjMatr" Binding="{Binding SubjMatr}" />
<DataGridTextColumn Header="Download %" Binding="{Binding IncValue}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
Content="Please Select" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<interact:Interaction.Triggers>
<interact:EventTrigger EventName="Checked">
<interact:InvokeCommandAction CommandParameter="{Binding Path=CaseRefNo}" Command="{Binding Path=CheckCommand}" />
</interact:EventTrigger>
</interact:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding IncValue, UpdateSourceTrigger=PropertyChanged}" Background="Red" Foreground="White" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Button" HorizontalAlignment="Left" Margin="672,20,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
I have created a custom control derived from a ListBox and I am having issues getting the "SelectedItemsList" to bind to it's corresponding property in the view model.
The problem: The selected items in the list box do not make it into the property it is bound to in the view model. The list box allows multiple selections but none of these make into the List in the view model.
MultiSelectListBox:
public class MultiSelectListBox : ListBox
{
public MultiSelectListBox() { }
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register(
"SelectedItemsList",
typeof(IList),
typeof(MultiSelectListBox),
new PropertyMetadata(default(IList)));
public IList SelectedItemsList
{
get { return (IList) GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
}
declaration in MainWindow.xaml:
<local:MultiSelectListBox
DataContext="{StaticResource viewModel}"
DockPanel.Dock="Left"
Visibility="{Binding IsThailandFinal, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}"
ItemsSource="{Binding SelectedOutputtapeList}"
SelectionMode="Multiple"
SelectedItemsList="{Binding SelectedOutputTapes, Mode=TwoWay}"
HorizontalAlignment="Right"
Background="DeepSkyBlue"
Foreground="MidnightBlue"
ScrollViewer.VerticalScrollBarVisibility="Visible"
Height="100"
Width="70"
Margin="5"/>
View Model (simplified):
public class BTLogFrontEndViewModel : ViewModelBase
{
private List<string> selectedOutputTapes;
public BTLogFrontEndViewModel()
{
selectedOutputTapes = new List<string>();
}
public List<string> SelectedOutputTapes
{
get
{
return selectedOutputTapes;
}
set
{
selectedOutputTapes = value;
OnPropertyChanged("SelectedOutputTapes");
}
}
}
One way is to not use a custom ListBox and use objects in your list that extend INotifyPropertyChanged:
<ListBox
Width="70"
Height="100"
HorizontalAlignment="Right"
Margin="5"
Background="DeepSkyBlue"
DockPanel.Dock="Left"
Foreground="MidnightBlue"
ItemsSource="{Binding SelectedOutputtapeList}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Multiple"
DisplayMemberPath="Description"
Visibility="{Binding IsThailandFinal, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Assuming BTLogFrontEndViewModel is your DataContext:
public class BTLogFrontEndViewModel : ViewModelBase
{
private ObservableCollection<OutputTapeViewModel> m_SelectedOutputtapeList;
public ObservableCollection<OutputTapeViewModel> SelectedOutputtapeList
{
get
{
return m_SelectedOutputtapeList;
}
set
{
m_SelectedOutputtapeList = value;
NotifyPropertyChanged("SelectedOutputtapeList");
}
}
}
Where OutputTapeViewModel is declared like:
public class OutputTapeViewModel : ViewModelBase
{
private string m_Description;
public string Description
{
get
{
return m_Description;
}
set
{
m_Description = value;
NotifyPropertyChanged("Description");
}
}
private bool m_IsSelected;
public string IsSelected
{
get
{
return m_IsSelected;
}
set
{
m_IsSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
}
Important things to note is on the listbox I have added the DisplayMemberPath property for use of the description field in the OutputTapeViewModel class for what to show on the listbox. Also it has an item container style, which binds to the OutputTapeViewModel.IsSelected property when selected in the ListBox. This OutputTapeViewModel.IsSelected allows you to use the BTLogFrontEndViewModel.SelectedOutputtapeList property in such ways as:
var selectedItems = SelectedOutputtapeList.Where(item => item.IsSelected);
This only works in your case if you don't care about the overhead of doing the LINQ expression.
At code behind, I have list of code with their descriptions fetched from a database.
eg: {(code1, Description 1), (code2, Description 2)}
i'm storing this as Dictionary<string, string> StatusText in my code behind.
the data source for the page is an xml file viz loaded into XmlFile and set as the data context for the page. Reports is a member of XmlFile and is an array which is the source for the data grid.
The 'Status' column of datagrid is bound to Reports.StatusCode. I need to use StatusCode to look up the value from StatusText and display the value part in the 'Status' column.
Now that i need to display the corresponding description for the code value in the DataGrid, i'm almost lost. the datagrid has to be non-editable, hence i prefer DataGridTextColumn or similar for the column type.
I'm comparatively new to wpf.
XAML
<Page x:Class="Project1.ApprovalQueue"
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"
Title="ApprovalQueue" Loaded="Page_Loaded"
mc:Ignorable="d">
<Page.Resources>
<uc:TextToVisiblityConvertor x:Key="TextToVisiblity" />
</Page.Resources>
<Page.DataContext>
<h:XmlFile x:Name="XmlData" />
</Page.DataContext>
<Grid Name="GrdData" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
<DataGrid Name="DGrid" HorizontalAlignment="Stretch"
AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False"
CanUserSortColumns="False" HeadersVisibility="Column" IsReadOnly="True" VerticalScrollBarVisibility="Auto" SelectionChanged="DGrid_SelectionChanged"
ItemsSource="{Binding Path=Reports, NotifyOnSourceUpdated=True}">
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="DueStatus" Width="16">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- ReSharper disable Xaml.BindingWithContextNotResolved - This will be generated at Runtime -->
<Image Source="{Binding ElementName=ImgOverDue, Path=Source}" Height="16" Width="16" Visibility="{Binding Converter={StaticResource TextToVisiblity}, Path=OverDue}"/>
<!-- ReSharper restore Xaml.BindingWithContextNotResolved -->
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="IncompleteStatus" Width="16">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- ReSharper disable Xaml.BindingWithContextNotResolved - This will be generated at Runtime -->
<Image Source="{Binding ElementName=ImgIncomplete, Path=Source}" Height="16" Width="16" Visibility="{Binding Converter={StaticResource TextToVisiblity}, Path=InComplete}"/>
<!-- ReSharper restore Xaml.BindingWithContextNotResolved -->
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn x:Name="RptStatus" Width="100" Header="Status" Binding="{Binding Path=StatusCode}" />
<DataGridTextColumn x:Name="RptDesc" Header="Report Description" Binding="{Binding Path=Description}"/>
<DataGridTextColumn x:Name="RptState" Width="150" Header="Current State" />
<DataGridTextColumn x:Name="RptDate" Width="100" Header="Submit Date" Binding="{Binding Path=SubmitDate}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Page>
The code behind
public partial class ApprovalQueue
{
public Dictionary<string, string> StatusText = new Dictionary<string, string>();
public ApprovalQueue()
{
InitializeComponent();
DataTable dt = MyConnection.GetLookupData("GetTrans", "report_status", int.MinValue);
foreach (DataRow row in dt.Rows)
{
StatusText.Add(Convert.ToString(row[0]), Convert.ToString(row[1]));
}
var fileInfo = new FileInfo(#"D:\XForms\APPQREFRESH.XML");
ProcessAppQueue(ref fileInfo);
}
private void ProcessAppQueue(ref FileInfo fileInfo)
{
var serializer = new XmlSerializer(typeof (XmlFile));
var reader = new StreamReader(fileInfo.FullName);
XmlData = (XmlFile) serializer.Deserialize(reader);
reader.Close();
DGrid.ItemsSource = XmlData.Reports;
Debug.WriteLine("here");
}
private void DGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//DataRow dRow =
//TxtDesc.Text = Convert.ToString(dSet.Tables["REPORT"].Rows[grid.CurrentRowIndex]["DESC"]);
}
}
the data source
namespace Helper
{
[Serializable]
[XmlRoot("XML_FILE")]
public class XmlFile : INotifyPropertyChanged
{
private Report[] _reports;
[XmlArray("REPORTS")]
[XmlArrayItem("REPORT", typeof (Report))]
public Report[] Reports
{
get { return _reports; }
set
{
if (value != _reports)
{
_reports = value;
NotifyPropertyChanged("Reports Changed");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
[Serializable]
public class Report : INotifyPropertyChanged
{
private string _reportType;
[XmlAttribute("TYPE")]
public string ReportType
{
get { return _reportType; }
set
{
if (value != _reportType)
{
_reportType = value;
NotifyPropertyChanged("ReportType");
}
}
}
[XmlElement("FILE")]
public string FileName { get; set; }
[XmlElement("S_DT")]
public string SubmitDate { get; set; }
[XmlElement("STAT")]
public string StatusCode { get; set; }
[XmlElement("OVER")]
public string OverDue { get; set; }
[XmlElement("INCM")]
public string InComplete { get; set; }
[XmlElement("DESC")]
public string Description { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
Ok, so your problem is that you're trying to display something that does not exist in the collection that is data bound to your DataGrid. The answer is simple... add it into the collection. This is one way that you could do this before you set it as the DataGrid.ItemsSource:
foreach (Report report in Reports)
{
report.StatusCode = StatusText[report.StatusCode];
}
This will replace the codes in the StatusCode property with the description. If you don't want to replace that value, then you could just add an extra column into your Report class:
public string StatusDescription { get; set; }