WPF TextBox Binding Desync - c#

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();
}
}
}

Related

C# Databinding Textblock, PropertyChanged stays null

Im trying to change 2 textblocks with data binding. The propertyChanged is always null, so the ui wont update.
This is my model code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MovieApp.Models
{
public class MovieModel : INotifyPropertyChanged
{
string original_title, overview;
public event PropertyChangedEventHandler PropertyChanged;
public string Original_Title {
get
{
return original_title;
}
set
{
original_title = value;
onPropertyChanged(nameof(Original_Title));
}
}
public string Overview
{
get
{
return overview;
}
set
{
overview = value;
onPropertyChanged(nameof(Overview));
}
}
protected void onPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
//PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The mainview.xaml.cs:
using MovieApp.API;
using MovieApp.Models;
using MovieApp.Processor;
using System.Windows;
namespace MovieApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
//private readonly MovieModel movieModel = new MovieModel();
public MainWindow()
{
InitializeComponent();
ApiCaller.InitializeClient();
// DataContext = movieModel;
}
private async void previousImageButton_Click(object sender, RoutedEventArgs e)
{
int id = 484718;
await MovieProcessor.LoadMovie(id);
}
private async void nextImageButton_Click(object sender, RoutedEventArgs e)
{
int id = 527774;
await MovieProcessor.LoadMovie(id);
}
}
}
and the maindwindow.xaml:
<Window x:Class="MovieApp.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:MovieApp.Models"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:MovieModel x:Key="movieModel" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button x:Name="previousImageButton" Padding="15" Margin="15" Click="previousImageButton_Click">Previous</Button>
<StackPanel Grid.Row="1">
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=Original_Title}" ></TextBlock>
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=Overview }"></TextBlock>
</StackPanel>
<Button Grid.Row="2" x:Name="nextImageButton" Padding="15" Margin="15" Click="nextImageButton_Click">Next</Button>
</Grid>
</Window>
EDIT:
Added the movieprocessor code:
using MovieApp.API;
using MovieApp.Models;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace MovieApp.Processor
{
class MovieProcessor
{
public static async Task<MovieModel> LoadMovie(int id)
{
string url = $"movie/{id}?api_key=77e7d2ef687aedca2119680778f1d619&language=en-US";
using (HttpResponseMessage response = await ApiCaller.httpClient.GetAsync(url))
{
if (response.IsSuccessStatusCode)
{
MovieModel movie = await response.Content.ReadAsAsync<MovieModel>();
Console.WriteLine(movie.Original_Title);
Console.WriteLine(movie.Overview);
return movie;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
}
I have no idea what could be wrong. I tried multiple things but nothing seemed to work for me. I tried adding datacontext but that didnt work either. I let it commented in my code so anyone can see it.
If your MovieProcess class sets the value of MovieModel.Original_Title and MovieModel.Overview property then you have to ensure that MovieProcess is accessing the same instance of MovieModel as your view (xaml).
Instead of using StaticResource movieModel assing DataContext in code behind.
private readonly MovieModel movieModel = new MovieModel();
public MovieProcessor MovieProcessor { get; set; }
public MainWindow()
{
InitializeComponent();
ApiCaller.InitializeClient();
DataContext = movieModel;
MovieProcessor = new MoviewProcessor(moviewModel);
}
Xaml
<StackPanel Grid.Row="1">
<TextBlock Text="{Binding Original_Title}" />
<TextBlock Text="{Binding Overview }" />
</StackPanel>
MovieProcessor class
public class MovieProcessor
{
private readonly MovieModel movieModel;
public MovieProcessor(MovieModel movieModel)
{
this.movieModel = movieModel;
}
public async Task LoadMovie(int id)
{
...
movieModel.Original_Title = <loaded_movie_title>;
movieModel.Overview = <loaded_movie_overview>;
...
}
}
I bind the command to the Buttons in the MovieProcessor to show data in Maindwindow with StaticResource movieModel, below is my code:
NotifyObject.cs
public class NotifyObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MyCommand.cs
public class MyCommand : ICommand
{
private Func<object, bool> _canExecute;
private Action<object> _execute;
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
{
CommandManager.RequerySuggested += value;
}
}
remove
{
if (_canExecute != null)
{
CommandManager.RequerySuggested -= value;
}
}
}
public bool CanExecute(object parameter)
{
if (_canExecute == null) return true;
return _canExecute(parameter);
}
public void Execute(object parameter)
{
if (_execute != null && CanExecute(parameter))
{
_execute(parameter);
}
}
public MyCommand(Action<object> execute) : this(execute, null)
{
}
public MyCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
}
MovieProcessor.cs
public class MovieProcessor:NotifyObject
{
private MovieModel vm;
public MovieModel VM
{
get { return vm; }
set
{
vm = value;
OnPropertyChange("VM");
}
}
public MovieModel LoadMovie(int id)
{
//....
}
private MyCommand _cmd1;
public MyCommand Cmd1
{
get
{
if (_cmd1 == null)
_cmd1 = new MyCommand(new Action<object>
(
o =>
{
int id = 484718;
LoadMovie(id);
}
));
return _cmd1;
}
}
private MyCommand _cmd2;
public MyCommand Cmd2
{
get
{
if (_cmd2 == null)
_cmd2 = new MyCommand(new Action<object>
(
o =>
{
int id = 527774;
LoadMovie(id);
}
));
return _cmd2;
}
}
}
MainWindow.xaml
<Window.Resources>
<local:MovieProcessor x:Key="movieModel" />
</Window.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button x:Name="previousImageButton" Padding="15" Margin="15" Command="{Binding Source={StaticResource movieModel}, Path=Cmd1}">Previous</Button>
<StackPanel Grid.Row="1">
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=VM.Original_Title}" ></TextBlock>
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=VM.Overview }"></TextBlock>
</StackPanel>
<Button Grid.Row="2" x:Name="nextImageButton" Padding="15" Margin="15" Command="{Binding Source={StaticResource movieModel}, Path=Cmd2}">Next</Button>
</Grid>

WPF ListBox Not updating

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.

Change Enabled State of Button immediately on any change in a TextBox?

I use data binding and command binding to set the enabled state of a button, depending on whether a particular string property has a value or not. Or you might say, I have a mandatory TextBox, and I want the user to not be able to click Ok before at least 1 character has been entered.
My code does exactly that, only that the enabled state of the button is not updated before the TextBox is unfocused, e.g. by pressing the Tab key. I want this to happen immediately, on any change of the TextBox content. How can I achieve this? Without breaking out of MVVM, of course!
View:
<Window x:Class="Gebietsmanager.GebietBearbeitenDlg.View"
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:Gebietsmanager.GebietBearbeitenDlg"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:ViewModel}"
Title="Gebiet bearbeiten" Height="110" Width="300" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" ResizeMode="NoResize">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Margin="8,8,0,0">Name:</Label>
<TextBox Grid.Column="1" Text="{Binding Name}" Margin="8,8,8,0"/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="8,8,0,0">
<Button IsDefault="True" Command="{Binding Commit}">Ok</Button>
<Button Command="{Binding Rollback}" Margin="8,0,0,0">Reset</Button>
<Button IsCancel="True" Margin="8,0,0,0">Cancel</Button>
</StackPanel>
</Grid>
</Window>
ViewModel:
using System.ComponentModel;
namespace Gebietsmanager.GebietBearbeitenDlg
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel(Gebiet gebiet)
{
_gebiet = gebiet;
_gebietCopy = new Gebiet();
Helpers.CopyPropValues(_gebietCopy, gebiet);
Commit = new Command(
() => Helpers.CopyPropValues(_gebiet, _gebietCopy),
() => !string.IsNullOrEmpty(_gebietCopy.Name));
Rollback = new Command(DoRollback);
}
private readonly Gebiet _gebiet;
private readonly Gebiet _gebietCopy;
private void DoRollback()
{
Helpers.CopyPropValues(_gebietCopy, _gebiet);
OnPropertyChanged();
}
public string Name
{
get { return _gebietCopy.Name; }
set
{
if (_gebietCopy.Name != value)
{
_gebietCopy.Name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public Command Commit { get; private set; }
public Command Rollback { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Command implementation:
using System;
using System.Windows.Input;
namespace Gebietsmanager
{
public sealed class Command : ICommand
{
public Command(Action executeAction, Func<bool> canExecutePredicate = null)
{
_executeAction = executeAction;
_canExecutePredicate = canExecutePredicate;
}
private readonly Action _executeAction;
private readonly Func<bool> _canExecutePredicate;
public void Execute(object parameter)
{
_executeAction?.Invoke();
}
public bool CanExecute(object parameter)
{
return _canExecutePredicate?.Invoke() ?? true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
You need to set UpdateSourceTrigger=PropertyChanged in your binding,
Example with MVVMLight:
XAML
<Window x:Class="WpfApplication2.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:local="clr-namespace:WpfApplication2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Window.DataContext>
<local:MyModel />
</Window.DataContext>
<Grid>
<StackPanel>
<TextBlock Text="Name" />
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Go !" IsEnabled="{Binding IsReady}" />
</StackPanel>
</Grid>
</Window>
Code
internal class MyModel : ViewModelBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
Set(() => Name, ref _name, value);
RaisePropertyChanged(() => IsReady);
}
}
public bool IsReady
{
get { return !string.IsNullOrEmpty(Name); }
}
}

How to bind boolean value to label

I am new to WPF. My code is as follows:
In my MainWindow.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Label HorizontalAlignment="Left" Margin="63,30,0,0" Grid.Row="0" VerticalAlignment="Top" Content="{Binding myVal}" Height="39" Width="71"/>
<Button Grid.Row="1" x:Name="btnSelect" Content="Select" Click="btnSelect_Click_1" Margin="396,0,10,0"/>
</Grid>
and MainWindow.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool _myboolVal;
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void btnSelect_Click_1(object sender, RoutedEventArgs e)
{
if (myVal== false)
{
myVal = true;
}
else
{
myVal= true;
}
}
public bool myVal
{
get { return _myboolVal; }
set { _myboolVal= value; OnPropertyChanged("myVal"); }
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
But the value of the label is always false.
Your logic in btnSelect_Click_1 is incorrect. Update it to:
private void btnSelect_Click_1(object sender, RoutedEventArgs e)
{
myVal = !myVal;
}
take a look at this sample (MVVM, Command binding, MVVMLight)
Please set the Mode to TwoWay ,then it will works.
Content="{Binding myVal,Mode=TwoWay}"

WPF: Binding a List<class> to a ComboBox

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.

Categories