I want to be able to bind a ListBox to a List<> or ObservableCollection<> and be able to change the List itself while keeping the binding.
In ViewModel:
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private ObservableCollection<string> items = new ObservableCollection<string>();
public ObservableCollection<string> Items
{
get
{
return items;
}
set
{
items = value;
RaisePropertyChangedEvent(nameof(Items));
}
}
private void FillItems()
{
Items1 = new ObservableCollection<string>();
Items.Add("1");
Items.Add("2");
}
in View:
<ListBox x:Name="listBox" ItemsSource="{Binding Items}"/>
Now when I call the FillItems() function, the list won't show items. But if I change the code like below it will work:
private void FillItems()
{
Items.Clear();
Items.Add("1");
Items.Add("2");
}
Insure that the view model implements INotifyPropertyChanged and notifies the view when ever the target property changes.
Using the following simple View
<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">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ListBox x:Name="listBox" ItemsSource="{Binding Items}"/>
<Button Grid.Row="1" Content="Fill" Click="Button_Click" />
</Grid>
</Window>
Along with the following code behind and view model
namespace WpfApplication1 {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
private MainViewModel viewModel;
public MainWindow() {
InitializeComponent();
viewModel = new MainViewModel() {
Items = new List<string>() {
"a", "b"
}
};
this.DataContext = viewModel;
}
private void Button_Click(object sender, RoutedEventArgs e) {
viewModel.FillItems();
}
}
public class MainViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Random random = new Random();
private List<string> items = new List<string>();
public List<string> Items {
get {
return items;
}
set {
items = value;
RaisePropertyChangedEvent(nameof(Items));
}
}
public void FillItems() {
var list = Enumerable.Range(0, 10).Select(i => random.Next(100).ToString()).Distinct().ToList();
Items = new List<string>(list);
}
}
}
Every time the button was clicked the ListBox was updated as expected for both List and ObservableCollection.
You should review your code again and make sure that the common practices that may be affecting your code are followed because what you provided in the example should work.
Related
Here is the method:
private void Capitales_SelectedChanged(object sender, RoutedEventArgs e)
{
string s = Capitales.SelectedItem.ToString();
tb.Text = "Selection: " + s;
}
I'm putting a list in the combobox, and when I compile the program, the textbox shows the next: ComboBox_MicroDocu.MainWindow+Ciudades, where "Ciudades" references my class.
You are writing WPF app as you whould do with Winform. This whould work, but there is a better way to do it. Use MVVM (Model View ViewModel).
MVVM is great since it allows you to decouple your views (xaml) from your business logic (viewModels). It's also great for testability.
Check out some good resources here https://www.tutorialspoint.com/mvvm/index.htm
this is how your code should looks like :
MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" ItemsSource="{Binding Elements}" SelectedItem="{Binding SelectedElement}"/>
<TextBox Grid.Column="1" Text="{Binding SelectedElement}"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
private string _selectedElement;
public IEnumerable<string> Elements
{
get
{
for(int i = 0; i < 10; i++)
{
yield return $"Element_{i}";
}
}
}
public string SelectedElement
{
get
{
return _selectedElement;
}
set
{
_selectedElement = value;
RaisePropertyChanged();
}
}
private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Admittedly I am new to wpf. But i have spent some time Googling about it all and I am stumped.
in essence i want to update my TextBlock in my UI using Binding whenever my Model values change.
So this is my Model:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
public class MyModel : INotifyPropertyChanged
{
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
public string MyField { get; set ; }
}
}
This is my UI:
<Window x:Class="WpfApplication1.MainWindow"
xmlns:viewModels="clr-namespace:WpfApplication1"
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"
d:DataContext="{d:DesignInstance viewModels:MyModel, IsDesignTimeCreatable=True}"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModels:MyModel></viewModels:MyModel>
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding MyModel.MyField}"></TextBlock>
<Button Content="Click Me!" Click="Button_Click" />
</StackPanel>
</Grid>
</Window>
This is my code behind:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MyModel myModel = new MyModel();
private void Button_Click(object sender, RoutedEventArgs e)
{
myModel.MyField = "has worked";
}
}
}
When i press the button the text does not change on the UI..?
The instance you create in the code behind is not the same as you assign in xaml.
Change the button click event to
private void Button_Click(object sender, RoutedEventArgs e)
{
var model = this.DataContext as MyModel;
model.MyField = "has worked";
}
And the binding in xaml to
<TextBlock Text="{Binding MyField}"></TextBlock>
And in your viewmodel you are not calling the notify property changed. So create a private field and modify the property as below.
private string myField;
public string MyField
{
get { return this.myField; }
set { this.SetProperty(ref this.myField, value); }
}
I have the following XAML:
<Window x:Class="ListBoxTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ListBoxTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Model />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Items}" Grid.Row="0"/>
<Button Content="Add" Click="Button_Click" Grid.Row="1" Margin="5"/>
</Grid>
</Window>
and the following code for the Model class, which is put into main window's DataContext:
public class Model : INotifyPropertyChanged
{
public Model()
{
items = new Dictionary<int, string>();
}
public void AddItem()
{
items.Add(items.Count, items.Count.ToString());
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Items"));
}
private Dictionary<int, string> items;
public IEnumerable<string> Items { get { return items.Values; } }
public event PropertyChangedEventHandler PropertyChanged;
}
and main window's code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var model = this.DataContext as Model;
model.AddItem();
}
}
When pressing the button, the contents of the list are not being updated.
However, when I change the getter of the Items property to this:
public IEnumerable<string> Items { get { return items.Values.ToList(); } }
it starts to work.
Then, when I comment out the part which sends PropertyChanged event it stops working again, which suggests that the event is being sent correctly.
So if the list receives the event, why can't it update its contents correctly in the first version, without the ToList call?
Raising the PropertyChanged event for the Items property is only effective if the property value has actually changed. While you raise the event, the WPF binding infrastructure notices that the collection instance returned by the property getter is the same as before and does nothing do update the binding target.
However, when you return items.Values.ToList(), a new collection instance is created each time, and the binding target is updated.
First, this is a simplified version from a wizard control using MVVM. The problem is just easier to reproduce as described below
After much narrowing down, I have resolved an infinite exception in my code to be due to the WPF ContentControl. However, I have yet to figure out how to handle it, other than try-catch wrapping all of my possible instantiation code. Here is sample code that reproduces this...any help on how to keep this infinite exception from occurring would be greatly appreciated.
Additional Details
To sum up, the problem is that if the content control changes its contents, and the thing being loaded in throws an exception, then it will throw, then retry the load, causing the throw again and again.
MainWindow.xaml
<Window x:Class="WpfApplication8.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" Name ="Main">
<Grid>
<ContentControl Name="bar" Content="{Binding ElementName=Main, Path=foo}"/>
<Button Click="ButtonBase_OnClick" Margin="20" Width="50"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private UserControl _foo;
public UserControl foo
{
get { return _foo; }
set { _foo = value; OnPropertyChanged("foo"); }
}
public MainWindow()
{
InitializeComponent();
foo = new UserControl1();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
foo = new UserControl2();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
UserControl1 is blank and all default
UserControl2.xaml.cs
public UserControl2()
{
InitializeComponent();
throw new Exception();
}
Do not bind ContentControl to MainWindow. Instead use DataTemplates to select the content for the MainWindow. One example-contrived way of doing it is to bind the ContentControl's Content to the DataContext of the MainWindow.
First some observable test data is needed. The specifics of this data are not important. The main point is to have two different classes of test data from which to choose - TestData.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace fwWpfDataTemplate
{
// Classes to fill TestData
public abstract class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Student : Person { }
public class Employee : Person
{
float _salary;
public float Salary
{
get { return _salary; }
set
{
_salary = value;
OnPropertyChanged("Salary");
}
}
}
public class TestData : ObservableCollection<Person>
{
public TestData()
: base(new List<Person>()
{
new Student { Name = "Arnold" },
new Employee { Name = "Don", Salary = 100000.0f }
}) { }
}
}
Then add DataTemplates to MainWindow's resources - MainWindow.xaml:
<Window x:Class="fwWpfDataTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:fwWpfDataTemplate"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type me:Student}">
<StackPanel>
<TextBlock Text="Student"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type me:Employee}">
<StackPanel>
<TextBlock Text="Employee"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="Salary"/>
<TextBlock Text="{Binding Salary}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Change Data Context" Click="Button_Click" />
<ContentControl Grid.Row="1" Content="{Binding}"/>
</Grid>
</Window>
Note: instead of the StackPanels the contents of the DataTemplates could be UserControl1, UserControl2, etc.
Then add some code to change the data context - MainWindow.cs:
using System.Windows;
namespace fwWpfDataTemplate
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
TestData testData = new TestData();
int testIndex = -1;
private void Button_Click(object sender, RoutedEventArgs e)
{
testIndex = (testIndex + 1) % testData.Count;
this.DataContext = testData[testIndex];
}
}
}
Enjoy.
I want to bind DataPager to DataGrid
here is xaml
<UserControl xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="NorthWindSilver.MainPage"
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:mv="clr-namespace:NorthWindSilver.ViewModel"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<mv:ViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<data:DataGrid Name="dgCustomer" AutoGenerateColumns="True" ItemsSource="{Binding Items, Mode=TwoWay, Source={StaticResource ViewModel}}">
</data:DataGrid>
<sdk:DataPager HorizontalContentAlignment="Center" x:Name="myPager" Grid.Row="2" Source="{Binding Path=ItemsSource, ElementName=dgCustomer}" PageSize="10"/>
</Grid>
and ViewModel
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Customer> _items;
public ViewModel()
{
if (!IsDesignTime)
this.Customer();
}
public void ChangeProperty(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ObservableCollection<Customer> Items
{
get
{
return this._items;
}
set
{
this._items = value;
ChangeProperty("Items");
}
}
public bool IsDesignTime
{
get
{
return (Application.Current == null) ||
(Application.Current.GetType() == typeof(Application));
}
}
public void Customer()
{
DataServiceClient webService = new DataServiceClient();
webService.GetCustomersCompleted += new EventHandler<GetCustomersCompletedEventArgs>(webService_GetCustomersCompleted);
webService.GetCustomersAsync();
}
void webService_GetCustomersCompleted(object sender, GetCustomersCompletedEventArgs e)
{
Items = e.Result;
PagedCollectionView pageView = new PagedCollectionView(Items);
MainPage ma = new MainPage();
ma.dgCustomer.ItemsSource = pageView;
}
}
Here is the result
As you see DataPager does not work
what the problem?
Try to expose your PagedCollectionView as a property on the ViewModel and bind the ItemsSource on the DataGrid and Pager to the PagedCollectionView instead.