I know this has been asked before, but still can't wrap my head around any solution that will work for my case. I want to be able to sort the observable collection in this project according to the ItemName. here is the full project https://gist.github.com/NewCoderNotInTown/322274bd7d2fd57bf2ae7784e1315b73.
the aim is to have both lists, the original one and the copied one, sorted alphabetically.
appreciate a solution for this case. as simple as possible for a beginner.
I Edit the question with complete code as advised.
Code for MainWindow.xaml
<Window x:Class="TwoWindows.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:WB="clr-namespace:TwoWindows"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Button Grid.Row="1" Content="Click Me" Width="80" Height="25" Click="ClickMe_Click"/>
<TabControl Grid.Row="0" Margin="10">
<TabItem>
<TabItem.Header>
<StackPanel>
<TextBlock Text="WindowB"/>
</StackPanel>
</TabItem.Header>
<WB:WindowB x:Name="_windowB"/>
</TabItem>
</TabControl>
</Grid>
Code for MainWindow.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TwoWindows
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ClickMe_Click(object sender, RoutedEventArgs e)
{
var windowBViewModel = new WindowBViewModel("WindowB");
var windowB = new WindowB();
var windowAViewModel = new WindowAViewModel("WindowA", windowBViewModel);
var windowA = new WindowA();
windowA.DataContext = windowAViewModel;
windowA.Show();
_windowB.DataContext = windowBViewModel;
}
}
}
Code for WindowA.xaml
<Window x:Class="TwoWindows.WindowA"
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:TwoWindows"
mc:Ignorable="d"
Title="WindowA" Height="300" Width="600">
<Grid>
<Label Content="{Binding PageTitle}" />
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding ItemName}" IsChecked="{Binding IsChecked}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Code for WindowA.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace TwoWindows
{
public partial class WindowA : Window
{
public WindowA()
{
InitializeComponent();
}
}
}
Code for WindowB.xaml
<UserControl x:Class="TwoWindows.WindowB"
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:TwoWindows"
mc:Ignorable="d">
<Grid>
<Label Content="{Binding PageTitle}" />
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ItemName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
Code for WindowB.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace TwoWindows
{
public partial class WindowB : UserControl
{
public WindowB()
{
InitializeComponent();
}
}
}
Code for WindowAViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TwoWindows
{
public class WindowAViewModel : BaseWindowViewModel
{
public WindowAViewModel(string pageTitle, WindowBViewModel windowBViewModel)
{
PageTitle = pageTitle;
MyItems.Add(new MyCustomItemViewModel("Apple", windowBViewModel));
MyItems.Add(new MyCustomItemViewModel("Orange", windowBViewModel));
MyItems.Add(new MyCustomItemViewModel("Banana", windowBViewModel));
}
}
}
Code for WindowBViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TwoWindows
{
public class WindowBViewModel : BaseWindowViewModel
{
public WindowBViewModel(string pageTitle)
{
PageTitle = pageTitle;
}
}
}
Code for MyCustomItemViewModel.cs
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TwoWindows
{
[ImplementPropertyChanged]
public class MyCustomItemViewModel
{
public string ItemName { get; set; }
public bool IsChecked { get; set; }
public WindowBViewModel WindowBViewModelObj { get; set; }
public MyCustomItemViewModel(string itemName, WindowBViewModel windowBViewModel)
{
ItemName = itemName;
WindowBViewModelObj = windowBViewModel;
}
private void OnIsCheckedChanged()
{
if (IsChecked)
WindowBViewModelObj.MyItems.Add(this);
else
WindowBViewModelObj.MyItems.Remove(this);
}
}
}
Code for BaseWindowViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TwoWindows
{
public class BaseWindowViewModel
{
public string PageTitle { get; set; }
public ObservableCollection<MyCustomItemViewModel> MyItems { get; } = new ObservableCollection<MyCustomItemViewModel>();
}
}
Here's three common ways for sorting from your code.
Manually apply the Sort anytime you add items. I would suggest linq for the actual sorting, although you can build your own. In your specific case, it would look like this :
MyItems.Add(new MyCustomItemViewModel("Apple", windowBViewModel));
MyItems.Add(new MyCustomItemViewModel("Orange", windowBViewModel));
MyItems.Add(new MyCustomItemViewModel("Banana", windowBViewModel));
MyItems = MyItems.Sort(p => p.ItemName);
This assumes you enable the setter so MyItems is not readonly.
In the getter for MyItems, return the collection sorted (and use a backing property for it). This is not really ideal, since you build the sorted collection for each call that is made to the getter. For your code, it would look like this :
public class BaseWindowViewModel
{
public string PageTitle { get; set; }
private ObservableCollection<MyCustomItemViewModel> _myItems = new ObservableCollection<MyCustomItemViewModel>()
public ObservableCollection<MyCustomItemViewModel> MyItems
{
get { return _myItems.Sort(p => p.ItemName); }
}
}
(Recommended) Work with a CollectionViewSource that sits on top of your collection and applies UI-friendly operations like sorting and filtering.
public class BaseWindowViewModel
{
public string PageTitle { get; set; }
private ObservableCollection<MyCustomItemViewModel> _myItems;
public ObservableCollection<MyCustomItemViewModel> MyItems
{
get
{
if (_myItems == null)
{
_myItems = new ObservableCollection<MyCustomItemViewModel>();
_myItemsSorted = CollectionViewSource.GetDefaultView(_myItems)
_myItemsSorted.SortDescriptions.Add(new SortDescription() { PropertyName = "ItemName" });
}
return _myItems;
}
}
private ICollectionView _myItemsSorted;
public ICollectionView MyItemsSorted { get { return _myItemsSorted; }}
}
And then just bind to MyItemsSorted rather than MyItems
You can do this using LINQ, but you'll have to call Sort() when you're done adding or removing items. If that happens a lot with large lists, you may want to look into ObservableCollection.Insert and using your own comparer.
public class BaseWindowViewModel
{
public string PageTitle { get; set; }
public ObservableCollection<MyCustomItemViewModel> MyItems
{
get
{
return _MyItems;
}
}
private ObservableCollection<MyCustomItemViewModel> _MyItems = new ObservableCollection<MyCustomItemViewModel>();
public void Sort()
{
_MyItems = new ObservableCollection<MyCustomItemViewModel>(from i in _MyItems orderby i.ItemName select i);
}
}
Related
I recently took up the task of learning how to build an application in WPF, and landed on ReactiveUI as my MVVM framework. I am currently trying to practice implementing the Router in my application, and I'm finding that despite following the examples from "You, I, and ReactiveUI", my RoutedViewHost is not displaying a view, and throws the error:
"System.Exception: 'Couldn't find view for 'LearnReactiveUI.ViewModels.StartupViewModel'.'"
Below is the xaml for my main window (ReactiveWindow), and has a RoutedViewHost as its body
<rxui:ReactiveWindow x:Class="LearnReactiveUI.Views.MainView"
xmlns:rxui="http://reactiveui.net"
xmlns:vms="clr-namespace:LearnReactiveUI.ViewModels"
x:TypeArguments="vms:MainViewModel"
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:LearnReactiveUI.Views"
mc:Ignorable="d"
Title="MainView" Height="450" Width="800">
<Grid>
<rxui:RoutedViewHost x:Name="routedViewHost"/>
</Grid>
</rxui:ReactiveWindow>
Here is my MainViewModel class, which creates a RoutingState and then navigates to a new StartupViewModel
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LearnReactiveUI.ViewModels
{
public class MainViewModel : ReactiveObject, IScreen
{
private readonly RoutingState routingState;
public MainViewModel()
{
this.routingState = new RoutingState();
routingState.Navigate.Execute(new StartupViewModel(this));
}
public RoutingState Router => this.routingState;
}
}
And finally here is my code-behind for my MainWindow that binds the Router to the RoutedViewHost
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using LearnReactiveUI.ViewModels;
using System.Reactive.Disposables;
namespace LearnReactiveUI.Views
{
public partial class MainView : ReactiveWindow<MainViewModel>
{
public MainView()
{
InitializeComponent();
this.ViewModel = new MainViewModel();
this.WhenActivated(disposables =>
{
this
.OneWayBind(this.ViewModel, vm => vm.Router, v => v.routedViewHost.Router)
.DisposeWith(disposables);
});
}
}
}
The code for my Startup view is also very simple. Here is the xaml
<rxui:ReactiveUserControl x:Class="LearnReactiveUI.Views.StartupView"
xmlns:rxui="http://reactiveui.net"
xmlns:vms="clr-namespace:LearnReactiveUI.ViewModels"
x:TypeArguments="vms:StartupViewModel"
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:LearnReactiveUI.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Label Content="Startup" HorizontalAlignment="Center"
VerticalAlignment="Center" FontSize="72"/>
</Grid>
</rxui:ReactiveUserControl>
And here is the code for the StartupViewModel
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LearnReactiveUI.ViewModels
{
public class StartupViewModel : ReactiveObject, IRoutableViewModel
{
private IScreen hostScreen;
public StartupViewModel(IScreen hostScreen)
{
this.hostScreen = hostScreen;
}
public string UrlPathSegment => "Startup";
public IScreen HostScreen => this.hostScreen;
}
}
There is no code in the code behind as there are no properties I am binding to the view yet.
My code compiles and I have verified that it will successfully instantiate a MainView and MainViewModel. I am trying to figure out where I went wrong.
Any help is appreciated. Thanks!
You need to register your view and viewModel. Please look at routing example.
In my opinion, this change in the MainViewModel constructor should fix the issue:
public MainViewModel()
{
this.routingState = new RoutingState();
// register view and viewModel
Locator.CurrentMutable.Register(() => new StartupView(), typeof(IViewFor<StartupViewModel>));
routingState.Navigate.Execute(new StartupViewModel(this));
}
#Glenn Watson mentions an important thing. The Locator setup should be done in a bootstrap-like class to allow multiple platform coding and to not break DI. You should look at this when you learn the basics.
Im not 100% this goes with the whole wpf mvvm design pattern but I'm lost on how to do it.
I have a main view in this main view i have ObservableCollection called 'DemoClass_List', each element in the list contains a class 'DemoClass'.
In the wpf for this view i have wrappanel, what i want is for the wrappanel to display a usercontrol for each element, but also to pass the individual class to the element.
Code is below.
***UserControl
public partial class DemoClass_Display : UserControl
{
public DemoClass _demoClass;
public DemoClass_Display()
{
InitialiseComponent();
//Do processing on _demoClass and display relevant info
}
}
***MainView WPF Snippet
<ItemsControl ItemsSource = "{Binding DemoClass_List}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<usercontrol:DemoClass_Display/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The question is, how do i fill the item "public DemoClass _demoClass;" in the usercontrol?
****************Edit****************
This code crashes
****MainView
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public static ObservableCollection<DemoClass> DemoClass_List { get; set; }
public Window1()
{
DemoClass_List = new ObservableCollection<DemoClass>();
DemoClass dc1 = new DemoClass();
dc1.Property1 = 1;
dc1.Property2 = 1;
dc1.Property3 = 1;
DemoClass dc2 = new DemoClass();
dc2.Property1 = 2;
dc2.Property2 = 2;
dc2.Property3 = 2;
DemoClass dc3 = new DemoClass();
dc3.Property1 = 3;
dc3.Property2 = 3;
dc3.Property3 = 3;
DemoClass_List.Add(dc1);
DemoClass_List.Add(dc2);
DemoClass_List.Add(dc3);
InitializeComponent();
}
}
}
***MainViewWPF
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UserControl="clr-namespace:WpfApplication1"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="Window1" Height="300" Width="300">
<Grid>
<ItemsControl ItemsSource="{Binding DemoClass_List}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<UserControl:DemoClass_Display demoClass="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
***UserControl
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for DemoClass_Display.xaml
/// </summary>
public partial class DemoClass_Display : UserControl
{
public static readonly DependencyProperty DemoClassProperty = DependencyProperty.Register("demoClass", typeof (DemoClass), typeof (DemoClass_Display), new PropertyMetadata(default(DemoClass)));
public DemoClass demoClass
{
get { return (DemoClass)GetValue(DemoClassProperty); }
set { SetValue(DemoClassProperty, value); }
}
public DemoClass_Display()
{
InitializeComponent();
Console.WriteLine(demoClass.Property1);
}
}
}
The console crashes as demoClass hasnt been set
You need to create a dependency property in your UserControl:
public static readonly DependencyProperty DemoClassProperty = DependencyProperty.Register(
"DemoClass", typeof (DemoClass), typeof (DemoClass_Display), new PropertyMetadata(default(DemoClass), OnDemoClassChanged));
public DemoClass DemoClass
{
get { return (DemoClass) GetValue(DemoClassProperty); }
set { SetValue(DemoClassProperty, value); }
}
private static void OnDemoClassChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var newDemoClass = e.NewValue as DemoClass;
var demoClassDisplay = (DemoClass_Display)dependencyObject; // you can access this UserControl this way.
if(newDemoClass != null)
{
//Do processing on newDemoClass and display relevant info
}
}
And then you can bind the current item of the ItemsControl in the DataTemplate:
<ItemsControl.ItemTemplate>
<DataTemplate>
<usercontrol:DemoClass_Display DemoClass="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
So inside the UserControl you will use the DemoClass property instead of the field _demoClass. I think that can even be deleted.
I have created very small WPF application and facing one problem. I have below classes.
Employee.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StaticResourceVsDynamicResource
{
public class Employee
{
public string strName;
public int nId;
public Employee()
{
strName = "Default name";
nId = -1;
}
public string Name
{
get{return strName;}set{strName = value;}
}
public int ID
{
get{return nId;}set{nId = value;}
}
}
}
MainWindow.xamal.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace StaticResourceVsDynamicResource
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
this.Resources["objEmployee"] = new Employee { Name = "Changed employee", ID = 100};
this.Resources.Add("myBrush",new SolidColorBrush(SystemColors.GrayTextColor));
}
}
}
MainWindow.xamal
<Window x:Class="StaticResourceVsDynamicResource.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StaticResourceVsDynamicResource"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<x:ArrayExtension Type="{x:Type sys:String}" x:Key="objNames">
<sys:String>A1</sys:String>
<sys:String>A2</sys:String>
</x:ArrayExtension>
<local:Employee x:Key="objEmployee"></local:Employee>
</Window.Resources>
<Grid>
<Grid Height="100" HorizontalAlignment="Left" Margin="281,12,0,0" Name="grid3" VerticalAlignment="Top" Width="200" >
<ComboBox ItemsSource="{StaticResource ResourceKey=objNames}" Height="23" HorizontalAlignment="Left" Margin="48,37,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" />
</Grid>
</Grid>
Above xaml code is intresting. When I build this code I didn't get any error. For whatever reason I just shuffle position of <x:ArrayExtension> and <local:Employee> and I start getting below error.
The name 'InitializeComponent' does not exist in the current context
When I am declaring <local:Employee> before <x:ArrayExtenion> then only I am getting this error. I am sure this has to do something with namespace, but I am not able to figure it out. See the below code which is causing compilation error.
<Window.Resources>
<local:Employee x:Key="objEmployee"></local:Employee>
<x:ArrayExtension Type="{x:Type sys:String}" x:Key="objNames">
<sys:String>A1</sys:String>
<sys:String>A2</sys:String>
</x:ArrayExtension>
</Window.Resources>
Can anyone help? Seems to be a strange problem but it is...
Regards,
Hemant
I've had the same problem. The way I resolved it was by changing the build action of the XAML file to Page.
To credit the source where I found the solution:
http://blog.mahop.net/post/Compile-Error-for-WPF-Files-The-name-InitializeComponent-does-not-exist-in-the-current-context.aspx
I am trying to bind an observable collection to a user control but it is not getting updated on user change but it is getting updated when the user control is changed through code. Following is an example i tried. It might be a bit long but it is working so you can copy and paste the code as it is.
Please see my question at the end of the post.
--Customer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace TestMVVM
{
class Customer : INotifyPropertyChanged
{
private string firstName;
private string lastName;
public string FirstName
{
get { return firstName; }
set
{
if (firstName != value)
{
firstName = value;
RaisePropertyChanged("FirstName");
}
}
}
public string LastName
{
get { return lastName; }
set
{
if (lastName != value)
{
lastName = value;
RaisePropertyChanged("LastName");
}
}
}
#region PropertChanged Block
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(property));
}
}
#endregion
}
}
--UCTextBox.xaml
<UserControl x:Class="TestMVVM.UCTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="40" Width="200">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="txtTextControl"
VerticalAlignment="Top" Width="120" />
</Grid>
--UCTextBox.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace TestMVVM
{
///
/// Interaction logic for UCTextBox.xaml
///
public partial class UCTextBox : UserControl, INotifyPropertyChanged
{
public UCTextBox()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(UCTextBox),
new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(textChangedCallBack)));
static void textChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
UCTextBox pasTextBox = (UCTextBox)property;
pasTextBox.txtTextControl.Text = (string)args.NewValue;
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
NotifyPropertyChanged("Text");
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
-- Window1.xaml
<Window x:Class="TestMVVM.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestMVVM"
Title="Window1" Height="300" Width="300">
<Grid>
<local:UCTextBox x:Name="txtUC" />
<Button Height="23" HorizontalAlignment="Left" Margin="39,0,0,82"
Name="btnUpdate" VerticalAlignment="Bottom" Width="75" Click="btnUpdate_Click">Update</Button>
<Button Height="23" Margin="120,0,83,82" Name="btnChange" VerticalAlignment="Bottom" Click="btnChange_Click">Change</Button>
</Grid>
-- Window1.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace TestMVVM
{
///
/// Interaction logic for Window1.xaml
///
public partial class Window1 : Window
{
CustomerHeaderViewModel customerHeaderViewModel = null;
public Window1()
{
InitializeComponent();
customerHeaderViewModel = new CustomerHeaderViewModel();
customerHeaderViewModel.LoadCustomers();
txtUC.DataContext = customerHeaderViewModel.Customers[0];
Binding binding = new Binding();
binding.Source = customerHeaderViewModel.Customers[0];
binding.Path = new System.Windows.PropertyPath("FirstName");
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
txtUC.SetBinding(UCTextBox.TextProperty, binding);
}
private void btnUpdate_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(customerHeaderViewModel.Customers[0].FirstName);
}
private void btnChange_Click(object sender, RoutedEventArgs e)
{
txtUC.Text = "Tom";
}
}
class CustomerHeaderViewModel
{
public ObservableCollection Customers { get; set; }
public void LoadCustomers()
{
ObservableCollection customers = new ObservableCollection();
customers.Add(new Customer { FirstName = "Jim", LastName = "Smith", NumberOfContracts = 23 });
Customers = customers;
}
}
}
When i run the Window1.xaml my user control shows the data as "Jim". Now when i change the text to say "John" and click on Update, the messagebox still shows "Jim" that means the observable collection is not getting updated. When i click on Change button the user control changes the data to "Tom". Now when i click on Update button the Messagebox shows "Tom". Can anyone please tell me how to achieve the updation of observable collection by changing the data in user control rather than through code?
That's because you're not handling the txtTextControl.TextChanged event, so your Text dependency property is never updated.
Anyway, you don't need to handle that manually with a DependencyPropertyChangedCallback and an event handler, you can just bind the txtTextControl.Text to the Text dependency property :
<TextBox Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="txtTextControl"
VerticalAlignment="Top" Width="120"
Text="{Binding Path=Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UCTextBox}}}"/>
An observable collection, observers the collection only. You will get notified when items are added or deleted not when fields of a single items did change. That is something completely different. Like Thomas Levesque said, you just need to bind the right property.
I ran into a problem today at work wherein I have a BindingGroup that has multiple ValidationRules that are failing simultaneously. Problem is, I get an ArgumentException bubbling up from withing BindingGroup.ValidateWithoutUpdate when I try and determine if there are any errors (i.e. to set the CanExecute on a command to false).
I've managed to distill it to the following example (sorry, it still crosses multiple sources, but I've enclosed all relevant parts that should be copy/pasteable into a new WPF project):
Window1.xaml:
<Window
x:Class="BindingGroupTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:BindingGroupTest"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="Window1" Height="300" Width="300">
<Window.BindingGroup>
<BindingGroup Name="RootBindingGroup">
<BindingGroup.ValidationRules>
<l:TestRule1 />
<l:TestRule2 />
</BindingGroup.ValidationRules>
</BindingGroup>
</Window.BindingGroup>
<StackPanel>
<TextBox Text="{Binding
Path=Name,
BindingGroupName=RootBindingGroup,
UpdateSourceTrigger=PropertyChanged,
diag:PresentationTraceSources.TraceLevel=High}" />
<TextBox Text="{Binding
Path=Age,
BindingGroupName=RootBindingGroup,
UpdateSourceTrigger=PropertyChanged,
diag:PresentationTraceSources.TraceLevel=High}" />
<Button Content="Validate" Click="DoValidate" />
</StackPanel>
</Window>
Window1.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace BindingGroupTest
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
this.DataContext = new Person()
{
Name="Test",
Age=30,
};
InitializeComponent();
this.BindingGroup.BeginEdit();
}
private void DoValidate(object sender, RoutedEventArgs e)
{
try
{
if (!this.BindingGroup.ValidateWithoutUpdate())
MessageBox.Show("Validation failed!");
else
MessageBox.Show("Validation passed!");
}
catch (Exception ex)
{
var msg = "While validating, caught exception: " + ex.Message;
MessageBox.Show(msg);
}
}
}
}
Person.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BindingGroupTest
{
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
TestRule1.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace BindingGroupTest
{
public class TestRule1 : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
var p = ((BindingGroup)value).Items[0] as Person;
if (p == null)
return ValidationResult.ValidResult;
if (p.Age < 0)
return new ValidationResult(false, "Surely, you've been born yet!");
return ValidationResult.ValidResult;
}
}
}
TestRule2.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace BindingGroupTest
{
public class TestRule2 : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
var p = ((BindingGroup)value).Items[0] as Person;
if (p == null)
return ValidationResult.ValidResult;
if (string.IsNullOrEmpty(p.Name))
return new ValidationResult(false, "What, no name?");
return ValidationResult.ValidResult;
}
}
}
Basically, my problem is that if both TestRule1 and TestRule2fail, I get anArgumentExceptionbubbling up when I callthis.BindingGroup.ValidateWithoutUpdate()with message "Cannot have duplicates in this Collection", parameter name: "validationError". I dug around through the implementation ofBindingGroup, and it seems that it is using itself as the second parameter toValidationError, which is thebindingInErrorparameter, which the underlyingValidationErrorCollection` requires to be unique.
Admittedly, this example is contrived, however, it perfectly demonstrates my real-world issue that is not. (I have 2, entirely independent, ValidationRules that are operating on different attributes of the same business object, and it would make zero sense to collapse these into a single ValidationRule). Additionally, every example I can find of using BindingGroup only ever demonstrates use of a single ValidationRule, but the construct clearly supports and accepts multiple rules, albeit, apparently, so long as only a single fails at a time.
Am I doing something wrong, or does this appear to be a bug in the BindingGroup implementation, as I'm inclined to think currently.
For what it's worth, I'm using VS2008 with .Net 3.5 SP1.
It will work the way you expect if you run it in .NET 4.0, so it looks like it was indeed a bug and has been fixed in the next release.