I searched in this forum but I was unable to find a solution for my specific scenario.
I`m trying to understand WPF and MVVM and I build a simple WPF for this.
My Data Model is (I Implemented INotifyPropertyChanged here and the constructor initializes all properties):
namespace MyApp.ui.Models
{
public class Server : INotifyPropertyChanged
{
private int id;
public int ID
{
get { return id; }
set { id = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged(Name); }
}
private string ipAddress;
public string IPAddress
{
get { return ipAddress; }
set { ipAddress = value; OnPropertyChanged(IPAddress); }
}
public Server(int ServerID, string ServerName, string ServerIpAddress)
{
ID = ServerID;
Name = ServerName;
IPAddress = ServerIpAddress;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null)
{
handler(this, new PropertyChangedEventArgs( propertyName ) );
}
}
}
}
My ViewModel (used by WPF Code Behind):
namespace MyApp.ui.ViewModels
{
public class ServersViewModel
{
private ObservableCollection<Server> server;
public ObservableCollection<Server> Servers
{
get { return server; }
set { server = value; }
}
public ServersViewModel()
{
Servers = new ObservableCollection<Server>
{
new Server(001, "Server001", #"192.168.254.3"),
new Server(002, "Server002", #"100.92.0.200"),
new Server(003, "Server003", #"64.32.0.3"),
new Server(004, "Server004", #"172.10.0.4"),
new Server(005, "Server005", #"165.23.0.233"),
new Server(006, "Server006", #"81.22.22.6"),
new Server(007, "Server007", #"10.10.0.7")
};
}
public void ChangeServerNames()
{
//Before Change
foreach (var item in Servers)
{
MessageBox.Show(item.Name);
}
int count = 1000;
foreach (var item in Servers)
{
item.Name = "Server" + count.ToString();
count += 1000;
}
//After Change
foreach (var item in Servers)
{
MessageBox.Show(item.Name);
}
}
}
}
My WPF Main View (Main Menu) loads a Custom user control (ExplorerView) with the following XAML code (Contains a listbox and each listbox item contains 1 checkbox + image + textblock)
<UserControl x:Class="MyApp.ui.Views.ExplorerView"
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:MyApp.ui.Views"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="200">
<Grid>
<ListBox ItemsSource="{Binding Servers}" Margin="2">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox VerticalContentAlignment="Center" Margin="4">
<StackPanel Orientation="Horizontal">
<Image Source="/resources/server64.png" Height="30" Margin="4"></Image>
<TextBlock Text="{Binding Name}"
VerticalAlignment="Center" Margin="4"></TextBlock>
</StackPanel>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
Finally the MainView Code Behind loads the ServersViewModel so the ExplorerView Control can Bind the data.
namespace MyApp.ui
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ServersViewModel context { get; set; }
public MainWindow()
{
InitializeComponent();
context = new ServersViewModel();
DataContext = context;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
context.ChangeServerNames();
}
}
}
That said, I have 2 Questions:
1) As you can see, in the MainView I implemented a Button click event that calls into ServersViewModel.ChangeServerNames() Method. The problem is that my TextBlock in ExplorerView Control does not show the updated data.
I ChangeServerNames() I also use a MessageBox to show the Values Before and After the change, and I see that the values are changing, not sure why the ListBox/TextBlock is not updating...!!! (I already tested many other possible solutions, but I can`t get it working...)
2) I read that the CodeBehind in MainView (and all other views) should only contain the InitializeComponent(); and "DataContext = context;" at Maximum...
If that is true, where the Events for button clicks and others should be placed?
Finally the code for the MainWindow XAML:
<Window
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:MyApp.ui"
xmlns:Views="clr-namespace:MyApp.ui.Views"
x:Class="MyApp.ui.MainWindow"
mc:Ignorable="d"
Title="Server" MinHeight="720" MinWidth="1024"
Height ="720" Width="1024">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="41*"/>
<RowDefinition Height="608*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<GridSplitter Grid.Column="1" Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Background="Gray"
ShowsPreview="True"
Width="4" Margin="0,2,0,4"
/>
<Views:MenuView Grid.ColumnSpan="3"/>
<Views:FooterView Grid.Row="2" Grid.ColumnSpan="3" />
<Views:ExplorerView Grid.Column="0" Grid.Row="1" />
<!--Temp Tests-->
<StackPanel Margin="12" Grid.Column="3" Grid.Row="1" Width="Auto" Height="Auto" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left">
<Button Margin="4" Width="120" Height="30" Content="Change Data Test..." Click="Button_Click" />
</StackPanel>
</Grid>
</Window>
Thank you for your time...
Ok, I found the problem...
Instead of
set { name = value; OnPropertyChanged(Name); }
set { ipAddress = value; OnPropertyChanged(IPAddress); }
I was missing the Quotesfor the String argument on method call
The correct form is
set { name = value; OnPropertyChanged("Name"); }
set { ipAddress = value; OnPropertyChanged("IPAddress"); }
Weird that the compiler didn`t throw any error.... The Method
private void OnPropertyChanged(string propertyName)
Is "Asking" for a string as input arg.
AnyWay the best to avoid these errors (that I found) is to write the event like this (The caller supplies it`s own Public Name):
private void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Now I can do
set { name = value; OnPropertyChanged(); }
set { ipAddress = value; OnPropertyChanged(); }
Thank you.
Related
I'm having an issue where binding a textbox seems to desync from the underlying property. The first time the underlying property is updated, the textbox does not update. Then, if the underlying property is updated again, the textbox is updated with the original update. This sliding window behavior seems to continue over time. Am I doing something stupid?
I'm using .NET 4.7, vs 2017 community
MainWindow.xaml
<Window x:Class="TextBoxBugTest.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:TextBoxBugTest"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="225">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="50*"/>
</Grid.ColumnDefinitions>
<TextBox Width="55" Height="23" Text="{Binding Test}"/>
<TextBox x:Name="test2" Grid.Column="1" Width="55" Height="23"/>
<Button Grid.Row="1" Grid.ColumnSpan="2" Height="23" Width="50"
Click="Button_Click" Content="update"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
MainWindowVM _vm;
private int counter = 1;
public MainWindow()
{
InitializeComponent();
_vm = new MainWindowVM();
DataContext = _vm;
test2.Text = "test";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var current = counter++;
_vm.Test = $"test{current}";
test2.Text = $"test{current}";
}
}
MainWindowVM.cs
public class MainWindowVM : INotifyPropertyChanged
{
private string _test;
public string Test
{
get { return _test; }
set
{
if (value != _test) on_prop_changed();
_test = value;
}
}
public MainWindowVM()
{
Test = "test";
}
public event PropertyChangedEventHandler PropertyChanged;
private void on_prop_changed([CallerMemberName] string prop = "")
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
Yes, I am such an idiot.
private string _test;
public string Test
{
get { return _test; }
set
{
if (value != _test) on_prop_changed();
_test = value;
}
}
should be
private string _test;
public string Test
{
get { return _test; }
set
{
if (value != _test)
{
_test = value;
on_prop_changed();
}
}
}
Problem
I am trying to bind a ComboBox's SelectedItem to a custom class but this does not update when the property is changed.INotifyPropertyChanged is implemented.
The DataContext
The DataContext is a custom class which contains many properties, but an extract of this is below. You can see it implements INotifyPropertyChanged and this called when the two properties are changed.
public class BctsChange : INotifyPropertyChanged
{
#region declarations
private byContact _Engineer;
public byContact Engineer
{
get { return _Engineer; }
set
{
_Engineer = value;
NotifyPropertyChanged("Engineer");
OnEngineerChanged();
}
}
private BctsSvc.DOSets _LeadingSet;
public BctsSvc.DOSets LeadingSet
{
get { return _LeadingSet; }
set { _LeadingSet = value; NotifyPropertyChanged("LeadingSet"); }
}
#endregion
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public BctsChange()
{
Engineer = new byContact(Environment.UserName);
}
private void OnEngineerChanged()
{
if (Engineer != null)
{
BctsSvc.DOSets leadSet = GetLeadingSetFromDeptCode(Engineer.DeptCode);
if (leadSet == null) return;
LeadingSet = leadSet;
}
}
private static BctsSvc.DOSets GetLeadingSetFromDeptCode(string DeptCode)
{
BctsSvc.BctsServiceSoapClient svc = new BctsSvc.BctsServiceSoapClient();
BctsSvc.DOSets setX = svc.GetSetFromDeptCode(DeptCode);
return setX;
}
}
The Window XAML
I have several controls on the window, but to keep the code simple I believe the following extract will suffice.
<Window x:Class="MyNamespace.wdSubmit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:MyNamespace"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="ucReqForm"
Title="wdSubmit" >
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<GroupBox Header="Engineer Details" Name="grpOwnerDetails" >
<StackPanel Orientation="Vertical">
<Grid VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="35"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Engineer.FullName, FallbackValue='Please select an engineer by clicking →', Mode=OneWay}" Margin="5,0" IsEnabled="True" FontStyle="Italic" />
<Button Content="{StaticResource icoSearch}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="1" Height="23" Name="btnSelectEngineer" Margin="0,0,5,0" HorizontalAlignment="Stretch" ToolTip="Search for an engineer responsible" Click="btnSelectEngineer_Click" />
</Grid>
<ComboBox Height="23" x:Name="ddSet2" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,NotifyOnTargetUpdated=True}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<my:LabelledDropdown Height="23" x:Name="ddSet" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,NotifyOnTargetUpdated=True,NotifyOnSourceUpdated=True}" Label="e.g. BodyHardware">
<my:LabelledDropdown.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</my:LabelledDropdown.ItemTemplate>
</my:LabelledDropdown>
</StackPanel>
</GroupBox>
</StackPanel>
</Window>
The above extract contains:
A Label that contains a contact's name, and a button to search for a contact, bound to the FullName of the Engineer
A ComboBox that contains departments within the company, bound to an ObservableCollection<DOSets>, which contains a list of departments
Two ComboBoxes, one which is a custom one and the other which is temporary to ensure the bug is not within the control. These are Databound to LeadingSet
Window Code Behind
In the code behind I set the DataContext to CurrentChange. When the user wants to select a different Engineer then this will update the selected department for the engineer in CurrentChange.
When the user changes the engineer, the data binding for the engineer is updated, but the selected department (Leading Set) isn't.
//Usings here
namespace MyNamespace
{
public partial class wdSubmit : Window, INotifyPropertyChanged
{
private BctsSvc.BctsServiceSoapClient svc;
private BctsChange _CurrentChange;
public BctsChange CurrentChange
{
get { return _CurrentChange; }
set { _CurrentChange = value; OnPropertyChanged("CurrentChange"); }
}
private List<BctsSvc.DOSets> _LeadingSets;
public List<BctsSvc.DOSets> LeadingSets
{
get
{
return _LeadingSets;
}
}
public wdSubmit()
{
InitializeComponent();
svc = new BctsSvc.BctsServiceSoapClient();
_LeadingSets = svc.GetLeadSets().ToList();
OnPropertyChanged("LeadingSets");
this._CurrentChange = new BctsChange();
this.DataContext = CurrentChange;
CurrentChange.PropertyChanged += new PropertyChangedEventHandler(CurrentChange_PropertyChanged);
}
void CurrentChange_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("CurrentChange");
OnPropertyChanged(e.PropertyName);
}
private void btnSelectEngineer_Click(object sender, RoutedEventArgs e)
{
byContact newContact = new frmSearchEngineer().ShowSearch();
if (newContact != null)
{
CurrentChange.Engineer = newContact;
PropertyChanged(CurrentChange, new PropertyChangedEventArgs("LeadingSet"));
PropertyChanged(CurrentChange.LeadingSet, new PropertyChangedEventArgs("LeadingSet"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(CurrentChange, new PropertyChangedEventArgs(propertyName));
}
}
}
I've realised the problem may be due to the LeadingSet, returned when the engineer is changed, being a different instance to that in the ObservableCollection.
I have two listboxes that both contain the same type of items and I have both their SelectedItem Property bound to the same Property on my ViewModel. I expected that, when I selected some item in List A, the Selection in List B disappears. But that is not the case. I need an ugly workaround to make it work how I expect it. Any hints how to make it work without this workaround?
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Items1}" SelectedItem="{Binding SelectedItem}" ></ListBox>
<ListBox Grid.Column="1" ItemsSource="{Binding Items2}" SelectedItem="{Binding SelectedItem}" ></ListBox>
<Button Grid.Row="1" Content="NULL" Grid.ColumnSpan="2" Click="ButtonBase_OnClick"></Button>
</Grid>
</Window>
C#
public partial class MainWindow : INotifyPropertyChanged
{
private Item _selectedItem;
public ObservableCollection<Item> Items1 { get; private set; }
public ObservableCollection<Item> Items2 { get; private set; }
public Item SelectedItem
{
get { return _selectedItem; }
set
{
// Uncomment this to make it work
//_selectedItem = null;
//OnPropertyChanged();
_selectedItem = value;
OnPropertyChanged();
}
}
public MainWindow()
{
Items1 = new ObservableCollection<Item>();
Items2 = new ObservableCollection<Item>();
Items1.Add(new Item("A1"));
Items1.Add(new Item("A2"));
Items2.Add(new Item("B1"));
Items2.Add(new Item("B2"));
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
SelectedItem = null;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Item
{
public Item(string name)
{
Name = name;
}
string Name { get; set; }
}
Why not binding them (TwoWay binding) to two different Properties (e.g. SelectedItem1 and SelectedItem2), Then:
public Item SelectedItem1
{
get { return _selectedItem1; }
set
{
_selectedItem1 = value;
OnPropertyChanged();
if(value!=null) //Here is the trick.
SelectedItem2=null;
OnPropertyChanged("SelectedItem");
}
}
EDIT: Then for the rest of your application code you can have a SelectedItem property like below. Also don't forget to add OnPropertyChanged("SelectedItem") in setter of both SelectedItem1 and SelectedItem2
public Item SelectedItem
{
get{return SelectedItem1 != null ? SelectedItem1 : SelectedItem2}
}
If it's an ugly workaround you want then tap the SelectionChanged event:
<ListBox x:Name="ListBoxA" ItemsSource="{Binding Items1}" SelectedItem="{Binding SelectedItem}" SelectionChanged="ListBox_SelectionChanged"></ListBox>
<ListBox x:Name="ListBoxB" Grid.Column="1" ItemsSource="{Binding Items2}" SelectedItem="{Binding SelectedItem}" SelectionChanged="ListBox_SelectionChanged" ></ListBox>
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender == this.ListBoxA)
this.ListBoxB.UnselectAll();
else
this.ListBoxA.UnselectAll();
}
Better yet, use an attached behaviour to do the same thing.
If I press a button "Check All" all CheckBoxes in a ListBox should be selected and added to a list where all checked items are stored. The problem is that only the visible checkboxes are updated properly.
Here is my CheckBoxListItem class:
public class Cbli : INotifyPropertyChanged
{
private string _name;
private Boolean _isChecked;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
public bool IsChecked
{
get { return _isChecked; }
set { _isChecked = value; OnPropertyChanged("IsChecked"); }
}
public override string ToString()
{
return string.Format("Name: {0}, IsChecked: {1}", _name, _isChecked);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Window x:Class="ListBoxBuggy.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:listBoxBuggy="clr-namespace:ListBoxBuggy"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}" WindowStartupLocation="CenterScreen">
<Window.Resources>
<DataTemplate x:Key="CheckBoxListItemTemplateNew" DataType="listBoxBuggy:Cbli">
<CheckBox Name="CheckBox"
IsChecked="{Binding IsChecked}"
Checked="Update"
Unchecked="Update"
FontSize="14">
<TextBlock Text="{Binding Name}" FontSize="14"/>
</CheckBox>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox HorizontalAlignment="Left" Height="300" VerticalAlignment="Top" Width="168"
ItemsSource="{Binding MyItemList}"
ItemTemplate="{StaticResource CheckBoxListItemTemplateNew}"
/>
<ListBox HorizontalAlignment="Left" Height="290" Margin="297,10,0,0" VerticalAlignment="Top" Width="195"
ItemsSource="{Binding CheckedItems}"
/>
<Button Content="Check All" HorizontalAlignment="Left" Margin="173,10,0,0" VerticalAlignment="Top" Width="75" Click="Check_All"/>
<Button Content="Uncheck All" HorizontalAlignment="Left" Margin="173,52,0,0" VerticalAlignment="Top" Width="75" Click="Uncheck_All"/>
</Grid>
</Window>
And the code behind:
public partial class MainWindow : Window
{
public ObservableCollection<Cbli> MyItemList { get; set; }
public ObservableCollection<Cbli> CheckedItems { get; set; }
public MainWindow()
{
// add dummy data
MyItemList = new ObservableCollection<Cbli>();
CheckedItems = new ObservableCollection<Cbli>();
for (int i = 0; i < 20; i++)
{
Cbli cbli = new Cbli
{
Name = "Test " + i,
IsChecked = i < 5 || i > 15
};
MyItemList.Add(cbli);
if (cbli.IsChecked)
CheckedItems.Add(cbli);
}
InitializeComponent();
}
private void Update(object sender, RoutedEventArgs e)
{
CheckBox selectedCheckbox = (CheckBox)sender;
Cbli cbli = (Cbli)selectedCheckbox.DataContext;
if (cbli.IsChecked)
CheckedItems.Add(cbli);
else
CheckedItems.Remove(cbli);
}
private void Check_All(object sender, RoutedEventArgs e)
{
foreach (Cbli cbli in MyItemList)
cbli.IsChecked = true;
}
private void Uncheck_All(object sender, RoutedEventArgs e)
{
foreach (Cbli cbli in MyItemList)
cbli.IsChecked = false;
}
}
After scrolling down, so all 20 items on the left list are visible and clicking then the "check all" button is working pretty well, but I don't know why.
Can someone please tell me what is wrong with that implementation? Checking/unchecking a single CheckBox is working, but the Check/Uncheck all buttons aren't working properly.
The comment from Blam (setting VirtualizingStackPanel.VirtualizationMode="Standard" was nearly the solution.
Add VirtualizingStackPanel.IsVirtualizing="False":
<ListBox HorizontalAlignment="Left" Height="300" VerticalAlignment="Top" Width="168"
ItemsSource="{Binding MyItemList}"
ItemTemplate="{StaticResource CheckBoxListItemTemplateNew}"
VirtualizingStackPanel.IsVirtualizing="False" />
This solved the problem (at least for me)
I've been working on this problem for about 3 hours now, and I got to a dead end.
Currently I'm trying to bind a list to a ComboBox.
I have used several methods to bind the List:
Code behind:
public partial class MainWindow : Window
{
public coImportReader ir { get; set; }
public MainWindow()
{
ir = new coImportReader();
InitializeComponent();
}
private void PremadeSearchPoints(coSearchPoint sp)
{
//SearchRefPoint.DataContext = ir.SearchPointCollection;
SearchRefPoint.ItemsSource = ir.SearchPointCollection;
SearchRefPoint.DisplayMemberPath = Name;
The data was binded correctly but the DisplayMemeberPath for some reason returned the name of the class and not the name of it's member.
The XAML method returned an empty string...
<ComboBox x:Name="SearchRefPoint" Height="30" Width="324" Margin="0,10,0,0"
VerticalAlignment="Top" ItemsSource="{Binding ir.SearchPointCollection}"
DisplayMemberPath="Name">
I've also tried to fill it with a new list which I create in the MainWindow. the result was the same in both cases.
Also I've tried to create and ListCollectionView which was success, but the problem was that I could get the index of the ComboBox item. I prefer to work by an Id. For that reason I was looking for a new solution which I found at: http://zamjad.wordpress.com/2012/08/15/multi-columns-combo-box/
The problem with this example is that is not clear how the itemsource is being binded.
Edit:
To sum things up: I'm currently trying to bind a list(SearchPointsCollection) of objects(coSearchPoints) defined in a class (coImportReader).
namespace Import_Rates_Manager
{
public partial class MainWindow : Window
{
public coImportReader ir;
public coViewerControles vc;
public coSearchPoint sp;
public MainWindow()
{
InitializeComponent();
ir = new coImportReader();
vc = new coViewerControles();
sp = new coSearchPoint();
SearchRefPoint.DataContext = ir;
}
}
}
//in function....
SearchRefPoint.ItemsSource = ir.SearchPointCollection;
SearchRefPoint.DisplayMemberPath = "Name";
namespace Import_Rates_Manager
{
public class coImportReader
{
public List<coSearchPoint> SearchPointCollection = new List<coSearchPoint>();
}
}
namespace Import_Rates_Manager
{
public class coSearchPoint
{
public coSearchPoint()
{
string Name = "";
Guid Id = Guid.NewGuid();
IRange FoundCell = null;
}
}
}
This results in a filled combobox with no text
Here a simple example using the MVVM Pattern
XAML
<Window x:Class="Binding_a_List_to_a_ComboBox.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">
<Grid HorizontalAlignment="Left"
VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" ItemsSource="{Binding SearchPointCollection , UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding MySelectedIndex, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Id}" Grid.Row="0"/>
<TextBlock Text="{Binding Name}" Grid.Row="1"/>
<TextBlock Text="{Binding Otherstuff}" Grid.Row="2"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Bind NOW" Grid.Column="0" Grid.Row="1" Click="Button_Click"/>
<TextBlock Text="{Binding MySelectedIndex, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="0"/>
<Grid Grid.Column="1" Grid.Row="1"
DataContext="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Id}" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" Grid.Column="1"/>
<TextBlock Text="{Binding SomeValue}" Grid.Column="2"/>
</Grid>
</Grid>
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using Import_Rates_Manager;
namespace Binding_a_List_to_a_ComboBox
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DataContext = new coImportReader();
}
}
}
namespace Import_Rates_Manager
{
public class coImportReader : INotifyPropertyChanged
{
private List<coSearchPoint> myItemsSource;
private int mySelectedIndex;
private coSearchPoint mySelectedItem;
public List<coSearchPoint> SearchPointCollection
{
get { return myItemsSource; }
set
{
myItemsSource = value;
OnPropertyChanged("SearchPointCollection ");
}
}
public int MySelectedIndex
{
get { return mySelectedIndex; }
set
{
mySelectedIndex = value;
OnPropertyChanged("MySelectedIndex");
}
}
public coSearchPoint MySelectedItem
{
get { return mySelectedItem; }
set { mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
}
}
#region cTor
public coImportReader()
{
myItemsSource = new List<coSearchPoint>();
myItemsSource.Add(new coSearchPoint { Name = "Name1" });
myItemsSource.Add(new coSearchPoint { Name = "Name2" });
myItemsSource.Add(new coSearchPoint { Name = "Name3" });
myItemsSource.Add(new coSearchPoint { Name = "Name4" });
myItemsSource.Add(new coSearchPoint { Name = "Name5" });
}
#endregion
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class coSearchPoint
{
public Guid Id { get; set; }
public String Name { get; set; }
public IRange FoundCell { get; set; }
public coSearchPoint()
{
Name = "";
Id = Guid.NewGuid();
FoundCell = null;
}
}
public interface IRange
{
string SomeValue { get; }
}
}
Here are 3 Classes:
MainWindow which set VM as his Datacontext
coImportReader the Class which presents your properties for your bindings
coSearchPoint which is just a Container for your information
IRange which is just an Interface
The Collection you are binding to needs to be a property of ir not a field.
Also try this :
public coImportReader ir { get; set; }
public <type of SearchPointCollection> irCollection { get { return ir != null ? ir.SearchPointCollection : null; } }
Bind to irCollection and see what errors you get if any.
The DisplayMemberPath should contain the property name of the elements in your collection. Assuming the elements in the SearchPointCollection are of the type SearchPoint and this class has a Property SearchPointName you should set DisplayMemberPath like this:
SearchRefPoint.DisplayMemberPath = "SearchPointName";
Edit:
In your code the class coSearchPoint has the Field Name defined in the Constructor. Name has to be a Property of the class, otherwise the Binding can't work.