I learn bindings in WPF via book. I have wrote such code:
using System;
namespace WpfBinding {
enum SomeColors {
Red,
Green,
Blue,
Gray
}
}
and
using System;
namespace WpfBinding {
class TestItem {
SomeColors color;
public TestItem(SomeColors color) {
Color = color;
}
internal SomeColors Color {
get { return color; }
set { color = value; }
}
public override string ToString() {
return Color.ToString();
}
}
}
XAML of my Window:
<Window x:Class="WpfBinding.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>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox x:Name="listBox" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="5"/>
<ComboBox x:Name="comboBox" HorizontalAlignment="Stretch"
VerticalAlignment="Top" Margin="5" Grid.Column="1"/>
</Grid>
</Window>
I have tried create binding through code:
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfBinding {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
// Data for listbox
TestItem[] items = new TestItem[] {
new TestItem(SomeColors.Red),
new TestItem(SomeColors.Green),
new TestItem(SomeColors.Green),
new TestItem(SomeColors.Red),
new TestItem(SomeColors.Blue),
new TestItem(SomeColors.Red),
};
// Create ObservableCollection item
ObservableCollection<TestItem> collection = new ObservableCollection<TestItem>(items);
listBox.ItemsSource = collection;// set data for listbox
comboBox.ItemsSource = Enum.GetValues(typeof(SomeColors)); // Get items from my enum
// Create bindings
Binding bind = new Binding();
bind.Source = listBox;
bind.Path = new PropertyPath("SelectedItem.Color");
bind.Mode = BindingMode.TwoWay;
comboBox.SetBinding(ComboBox.SelectedItemProperty, bind);
}
}
}
But my binding ain't working. Why?
Screen:
Here is the error -
System.Windows.Data Error: 40 : BindingExpression path error: 'Color' property
not found on 'object' ''TestItem' (HashCode=13974362)'.
BindingExpression:Path=SelectedItem.Color; DataItem='ListBox' (Name='listBox');
target element is 'ComboBox' (Name='comboBox'); target property is 'SelectedItem'
(type 'Object')
You need to make the property Color public instead of internal.
From MSDN here -
The properties you use as binding source properties for a binding must
be public properties of your class. Explicitly defined interface
properties cannot be accessed for binding purposes, nor can protected,
private, internal, or virtual properties that have no base
implementation.
It's always useful to watch the Output window of Visual Studio when debugging! Had you looked there, you'd have seen this:
System.Windows.Data Error: 40 : BindingExpression path error: 'Color' property not found on 'object' ''TestItem' (HashCode=20856310)'. BindingExpression:Path=SelectedItem.Color; DataItem='ListBox' (Name='listBox'); target element is 'ComboBox' (Name='comboBox'); target property is 'SelectedItem' (type 'Object')
Exactly, binding can be done with public properties only, so
internal SomeColors Color
should be
public SomeColors Color
I think the problem is that your classes are not implementing INotifyPropertyChanged.
In order for the bindings to know when a property has changed it's value, you have to send it notification, and you do that with INotifyPropertyChanged.
UPDATE
So your listbox is bound to an ObservableCollection which does provide change notifiations, but only to the list box and only if you add or remove items from the collections.
You might also want to enable WPF Binding trace information in visual studio (http://msdn.microsoft.com/en-us/library/dd409960%28v=vs.100%29.aspx) that might help you figure out what is going on too.
The last thing I noticed is that the Color property of your TestItem class is marked as internal. WPF won't have access to that property unless it's public.
Thanks all. I have edit my code:
using System;
using System.ComponentModel;
namespace WpfBinding {
public class TestItem : INotifyPropertyChanged{
SomeColors color;
public TestItem(SomeColors color) {
Color = color;
}
public SomeColors Color {
get { return color; }
set { color = value;
OnPropertyChanged("Color");
}
}
public override string ToString() {
return Color.ToString();
}
void OnPropertyChanged(String name) {
PropertyChangedEventHandler temp = PropertyChanged;
if (null != temp) {
temp(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
and
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
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 WpfBinding {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
// Data for listbox
TestItem[] items = new TestItem[] {
new TestItem(SomeColors.Red),
new TestItem(SomeColors.Green),
new TestItem(SomeColors.Green),
new TestItem(SomeColors.Red),
new TestItem(SomeColors.Blue),
new TestItem(SomeColors.Red),
};
// Create ObservableCollection item
ObservableCollection<TestItem> collection = new ObservableCollection<TestItem>(items);
listBox.ItemsSource = collection;// set data for listbox
ObservableCollection<SomeColors> collection2 = new
ObservableCollection<SomeColors>(Enum.GetValues(typeof(SomeColors)).Cast<SomeColors>());
comboBox.ItemsSource = collection2; // Get items from my enum
// Create bindings
Binding bind = new Binding();
bind.Source = listBox;
bind.Path = new PropertyPath("SelectedItem.Color");
bind.Mode = BindingMode.TwoWay;
comboBox.SetBinding(ComboBox.SelectedItemProperty, bind);
}
}
}
Look Screen, please:
Related
I am working on a WPF Projet, in which I have a view with two usercontrols on it. This is basically a UserControl with a grid on it and another one with a edit panel to edit the selected object in the DataGrid. The edit panel control, consists of textboxes to edit properties of the selected object in the other control and a button to save. What I would like to do is to pass the selected object to the edit panel,that is each time a object is selected in the grid, the edit panel updates to select that same object. What is the best way to do this, please help?An example would be super :0)
The best way to deal with this is using the MVVM pattern, where both of your user controls bind to the same ViewModel.
The grid can bind to your collection (List<>) of objects that you want to show, and it can also bind its SelectedRow/SelectedItem property to a corresponding property on the ViewModel called SelectedItem (or similar). This means that every time a row is selected in the grid, the underlying data object will be populated into the property on the ViewModel.
You then bind your details user control to the same SelectedItem property on the ViewModel. Check this very simple example of a DataGrid and TextBox binding to the same SelectedItem property:
ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace WpfApplication11
{
public class MyViewModel : INotifyPropertyChanged
{
public List<Customer> MyList
{
get { return _myList; }
set
{
_myList = value;
OnPropertyChanged("MyList");
}
}
public Customer SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private Customer _selectedItem;
private List<Customer> _myList;
}
public class Customer
{
public string Name { get; set; }
}
}
MainWindow
<Window x:Class="WpfApplication11.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:swm="clr-namespace:System.Windows.Media;assembly=WindowsBase"
xmlns:swm1="clr-namespace:System.Windows.Media;assembly=PresentationCore"
Title="MainWindow" Height="289" Width="525">
<Grid>
<DataGrid AutoGenerateColumns="True" Margin="12,12,12,38" Name="dataGrid1" ItemsSource="{Binding MyList}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
<Label Content="Name" Height="28" HorizontalAlignment="Left" Margin="12,222,0,0" Name="label1" VerticalAlignment="Top" Width="57" />
<TextBox Text="{Binding Path=SelectedItem.Name, Mode=TwoWay}" Height="23" HorizontalAlignment="Left" Margin="60,222,0,0" Name="textBox1" VerticalAlignment="Top" Width="267" />
</Grid>
</Window>
MainWindow code behind
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 WpfApplication11
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
MyViewModel vm = new MyViewModel();
vm.MyList = new List<Customer>(new Customer[] { new Customer() { Name = "Bugs Bunny" }, new Customer() { Name = "Elmer Fudd" } });
this.DataContext = vm;
}
}
}
If you run this and then select a row in the grid the name of the customer will be populated into the textbox underneath. If you then modify the name in the textbox and remove focus from it (TAB out of it) then the row in the datagrid will get updated with the new name - all through binding.
For further information, there have previously been a few thousand questions on Stack Overflow regarding the MVVM pattern with WPF, many of them specifically about master-detail views like the one you want.
In your XAML markup, just bind the edit panel's object to the selected object in the grid object.
Can I set the Content property of a ContentControl to a DrawingVisual object? It says in the documentation that the content can be anything but I tried and nothing shows up when I add the control to canvas. Is it possible and if it is can you post the full code that adds a ContentControl, whose content is a DrawingVisual, to a canvas?
Can I set the Content property of a ContentControl to a DrawingVisual object?
Technically, yes, you can. However, that is probably not what you want. A DrawingVisual added to a ContentControl will simply display the string "System.Windows.Media.DrawingVisual". The following code within a grid will demonstrate this easilly:
<Button>
<DrawingVisual/>
</Button>
To use a DrawingVisual properly, you need to encapsulate it within a FrameworkElement. See the Microsoft Reference.
Thus, the following code should help do what you want.
<Window x:Class="TestDump.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestDump"
Title="Window1" Height="300" Width="600" >
<Grid>
<Canvas>
<Button >
<local:MyVisualHost Width="600" Height="300"/>
</Button>
</Canvas>
</Grid>
</Window>
And on the C# side:
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 TestDump
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public class MyVisualHost : FrameworkElement
{
private VisualCollection _children;
public MyVisualHost()
{
_children = new VisualCollection(this);
_children.Add(CreateDrawingVisualRectangle());
}
// Create a DrawingVisual that contains a rectangle.
private DrawingVisual CreateDrawingVisualRectangle()
{
DrawingVisual drawingVisual = new DrawingVisual();
// Retrieve the DrawingContext in order to create new drawing content.
DrawingContext drawingContext = drawingVisual.RenderOpen();
// Create a rectangle and draw it in the DrawingContext.
Rect rect = new Rect(new System.Windows.Point(160, 100), new System.Windows.Size(320, 80));
drawingContext.DrawRectangle(System.Windows.Media.Brushes.Blue, (System.Windows.Media.Pen)null, rect);
// Persist the drawing content.
drawingContext.Close();
return drawingVisual;
}
// Provide a required override for the VisualChildrenCount property.
protected override int VisualChildrenCount
{
get { return _children.Count; }
}
// Provide a required override for the GetVisualChild method.
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= _children.Count)
{
throw new ArgumentOutOfRangeException();
}
return _children[index];
}
}
}
I am currently experimenting with WPF.
One thing, I wanted to do was a master to detail selection over multiple comboboxes.
I have a ViewModel with GroupItems that i use as ItemSource for the first combobox. These GroupItems have a Property called Childs, which includes a List of items that belong to this group.
I can't find a way to bind the comboBox1.SelectedItem.Childs as Itemsource for the second comboBox.
Right now I only got to
ItemsSource="{Binding ElementName=comboBox1, Path=SelectedItem}"
But I don't get the Property of the SelectedItem. How can this be done? Or is this not the WPF way to this?
Is there any good website to learn how to select different elements? Eplaining Path, XPath, Source and everything?
Thanks for any help.
Your binding above isn't attempting to bind to Childs, only SelectedItem.
Try something like this:
Window1.xaml
<Window x:Class="WpfApplication5.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">
<StackPanel>
<ComboBox x:Name="_groups" ItemsSource="{Binding Groups}" DisplayMemberPath="Name"/>
<ComboBox ItemsSource="{Binding SelectedItem.Items, ElementName=_groups}"/>
</StackPanel>
</Window>
Window1.xaml.cs
using System.Windows;
namespace WpfApplication5 {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
var model = new ViewModel();
var g1 = new Group { Name = "Group1" };
g1._items.Add("G1C1");
g1._items.Add("G1C2");
g1._items.Add("G1C3");
model._groups.Add(g1);
var g2 = new Group { Name = "Group2" };
g2._items.Add("G2C1");
g2._items.Add("G2C2");
g2._items.Add("G2C3");
model._groups.Add(g2);
var g3 = new Group { Name = "Group3" };
g3._items.Add("G3C1");
g3._items.Add("G3C2");
g3._items.Add("G3C3");
model._groups.Add(g3);
DataContext = model;
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
namespace WpfApplication5
{
public class Group {
internal List<String> _items = new List<string>();
public IEnumerable<String> Items {
get { return _items; }
}
public String Name { get; set; }
}
public class ViewModel
{
internal List<Group> _groups = new List<Group>();
public IEnumerable<Group> Groups
{
get { return _groups; }
}
}
}
I'm just playing around with WPF and MVVM, and I have made a simple app that displays a Rectangle that changes color whenever Network availability changes.
But when that happens, I get this error: Cannot use a DependencyObject that belongs to a different thread than its parent Freezable.
Code
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="400" Width="600">
<DockPanel LastChildFill="True">
<Rectangle x:Name="networkStatusRectangle" Width="200" Height="200" Fill="{Binding NetworkStatusColor}" />
</DockPanel>
</Window>
Code-behind
using System.Windows;
using WpfApplication1.ViewModels;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new NetworkViewModel();
}
}
}
ViewModel
using System.ComponentModel;
using System.Net.NetworkInformation;
using System.Windows.Media;
namespace WpfApplication1.ViewModels
{
public class NetworkViewModel : INotifyPropertyChanged
{
private Brush _NetworkStatusColor;
public Brush NetworkStatusColor
{
get { return _NetworkStatusColor; }
set
{
_NetworkStatusColor = value;
NotifyOfPropertyChange("NetworkStatusColor");
}
}
public NetworkViewModel()
{
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
}
protected void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
if (e.IsAvailable)
{
this.NetworkStatusColor = new SolidColorBrush(Colors.Green);
}
else
{
this.NetworkStatusColor = new SolidColorBrush(Colors.Red);
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void NotifyOfPropertyChange(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I assume that I should change the NetworkStatusColor property by invoking something?
You assume correctly. It's the Dispatcher class and the .Invoke method you want to take a look at.
Something a bit like this:
if (this.Dispatcher.Thread != Thread.CurrentThread)
{
this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(...your method...), any, params, here);
return
}
There's an MSDN article here with some more info.
With MVVM you have a couple of options when dealing with dispatching. Either you can send some kind of message to your view to have it invoke the operation for you, or you can create some kind of abstract dispatcher service that you are able to easily mock.
Take a look at the MVVM Light toolkit, as it includes a simple dispatcher-service you can use/copy.
Is there any way, how to force ObservableCollection to fire CollectionChanged?
I have a ObservableCollection of objects ListBox item source, so every time I add/remove item to collection, ListBox changes accordingly, but when I change properties of some objects in collection, ListBox still renders the old values.
Even if I do modify some properties and then add/remove object to the collection, nothing happens, I still see old values.
Is there any other way around to do this? I found interface INotifyPropertyChanged, but I don't know how to use it.
I agree with Matt's comments above. Here's a small piece of code to show how to implement the INotifyPropertyChanged.
===========
Code-behind
===========
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
Nicknames names;
public Window1()
{
InitializeComponent();
this.addButton.Click += addButton_Click;
this.names = new Nicknames();
dockPanel.DataContext = this.names;
}
void addButton_Click(object sender, RoutedEventArgs e)
{
this.names.Add(new Nickname(myName.Text, myNick.Text));
}
}
public class Nicknames : System.Collections.ObjectModel.ObservableCollection<Nickname> { }
public class Nickname : System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
void Notify(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propName));
}
}
string name;
public string Name
{
get { return name; }
set
{
name = value;
Notify("Name");
}
}
string nick;
public string Nick
{
get { return nick; }
set
{
nick = value;
Notify("Nick");
}
}
public Nickname() : this("name", "nick") { }
public Nickname(string name, string nick)
{
this.name = name;
this.nick = nick;
}
public override string ToString()
{
return Name.ToString() + " " + Nick.ToString();
}
}
}
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<DockPanel x:Name="dockPanel">
<TextBlock DockPanel.Dock="Top">
<TextBlock VerticalAlignment="Center">Name: </TextBlock>
<TextBox Text="{Binding Path=Name}" Name="myName" />
<TextBlock VerticalAlignment="Center">Nick: </TextBlock>
<TextBox Text="{Binding Path=Nick}" Name="myNick" />
</TextBlock>
<Button DockPanel.Dock="Bottom" x:Name="addButton">Add</Button>
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
</DockPanel>
</Grid>
Modifying properties on the items in your collection won't fire NotifyCollectionChanged on the collection itself - it hasn't changed the collection, after all.
You're on the right track with INotifyPropertyChanged. You'll need to implement that interface on the class that your list contains. So if your collection is ObservableCollection<Foo>, make sure your Foo class implements INotifyPropertyChanged.