I have an issue with something that should be very simple databinding scenario. I want to bind a list of items. I want to create a user control put it in a ItemsControl's template and bind the ItemsControl to some data. I am perfectly happy with one time databinding so I was kind of hoping to avoid learning about dependency properties and all the databinding stuff for this simple scenario.
Here is the XAML for the user control:
<TextBlock>Just Something</TextBlock>
And the code behind:
namespace TestWindowsPhoneApplication
{
public partial class TestControl : UserControl
{
public TestData SomeProperty { get; set; }
public String SomeStringProperty { get; set; }
public TestControl()
{
InitializeComponent();
}
}
}
MainPage.xaml:
<ItemsControl Name="itemsList" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<t:TestControl SomeStringProperty="{Binding Path=SomeString}"></t:TestControl>
<!--<TextBlock Text="{Binding Path=SomeString}"></TextBlock>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is MainPage.xaml.cs:
namespace TestWindowsPhoneApplication
{
public class TestData
{
public string SomeString { get; set; }
}
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
itemsList.DataContext = new TestData[] { new TestData { SomeString = "Test1" }, new TestData { SomeString = "Test2" } };
}
}
}
When I run the project I get an error "the parameter is incorrect". I also tried binding directly to the item with SomeProperty={Binding} since that is what I actually want to do but this causes the same error. If I try doing the same thing with the TextBlock control (the commented line) everything works fine.
How can I implement this simple scenario?
To make a property on your custom control "bindable" you have to make it a dependency property. Check out my answer here for a nice simple example of doing just this on a custom control: passing a gridview selected item value to a different ViewModel of different Usercontrol
public string SomeString
{
get { return (string)GetValue(SomeStringProperty); }
set { SetValue(SomeStringProperty, value); }
}
public static readonly DependencyProperty SomeStringProperty =
DependencyProperty.Register("SomeString", typeof(string), typeof(TestControl),
new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnSomeStringChanged)));
private static void OnSomeStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TestControl)d).OnSomeStringChanged(e);
}
protected virtual void OnSomeStringChanged(DependencyPropertyChangedEventArgs e)
{
//here you can do whatever you'd like with the updated value of SomeString
string updatedSomeStringValue = e.NewValue;
}
Related
Trying to understand how to bind this static list to a combobox that located on different window.
public partial class MainWindow : Window
{
public static List<Classes.Entity> EntityList { get; set; }
public MainWindow()
{
EntityList = new List<Classes.Entity>();
InitializeComponent();
}
...
the object:
public class Entity
{
public string entityName { get; set; }
...
XAML (In a diffrent window, call "NewRelationship.xaml.cs"
<ComboBox x:Name="cb_from" ItemsSource="{Binding Path=EntityList}" DisplayMemberPath="entityName" SelectedValue="{Binding Path=Entity}" />
Of course I fill the list later in the code...
if I moving the list to the newRelationship window and add "this.datacontext = this;" its working,
How do I make this work when the list is in the mainWindow? Thanks...
A better approach would be to keep the EntityList in a separate object that both windows could reference:
class ViewModel
{
private List<Classes.Entity> _entityList = new List<Classes.Entity>();
public IEnumerable<Classes.Entity> EntityList
{
get { return _entityList; }
}
}
partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
When the second window is created, you can pass an instance of the ViewModel class to it, and set it as the DataContext.
I want to learn how to use Dependency Objects and Properties. I have created this class,
public class TestDependency : DependencyObject
{
public static readonly DependencyProperty TestDateTimeProperty =
DependencyProperty.Register("TestDateTime",
typeof(DateTime),
typeof(TestDependency),
new PropertyMetadata(DateTime.Now));
public DateTime TestDateTime
{
get { return (DateTime) GetValue(TestDateTimeProperty); }
set { SetValue(TestDateTimeProperty, value); }
}
}
The window class is like this
public partial class MainWindow : Window
{
private TestDependency td;
public MainWindow()
{
InitializeComponent();
td = new TestDependency();
td.TestDateTime = DateTime.Now;
}
}
Now I want to use it to show a the current DateTime in the TextBlock which updates itself every second, by adding this to a grid
<Grid>
<TextBlock Text="{Binding TestDateTime,ElementName=td}" Width="200" Height="200"/>
</Grid>
I can see the TextBlock, but there is no Date Time value in it at all. What am I doing wrong?
First of all if you want to update the display time once a second your going to need a timer to trigger an update. A DispatchTimer works works well for that.
public class TestDependency : DependencyObject
{
public static readonly DependencyProperty TestDateTimeProperty =
DependencyProperty.Register("TestDateTime", typeof(DateTime), typeof(TestDependency),
new PropertyMetadata(DateTime.Now));
DispatcherTimer timer;
public TestDependency()
{
timer = new DispatcherTimer(new TimeSpan(0,0,1), DispatcherPriority.DataBind, new EventHandler(Callback), Application.Current.Dispatcher);
timer.Start();
}
public DateTime TestDateTime
{
get { return (DateTime)GetValue(TestDateTimeProperty); }
set { SetValue(TestDateTimeProperty, value); }
}
private void Callback(object ignore, EventArgs ex)
{
TestDateTime = DateTime.Now;
}
}
Next we need to modify the XAML so it binds properly to the updated dependency object.
<Window.DataContext>
<local:TestDependency/>
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding TestDateTime}" />
</Grid>
Since we set the DataContext in XAML you can actually delete all of the code behind code in the MainWindow constructor.
If you just want to show some values in your TextBlock, you don't need a Dependency Object here. Try something like this:
public partial class MainWindow : Window
{
public DateTime Test
{ get; set; }
public MainWindow()
{
InitializeComponent();
this.Test = DateTime.Now;
}
}
<Grid>
<TextBlock Text="{Binding Path=Test,RelativeSource={RelativeSource AncestorType=Window,Mode=FindAncestor}}"></TextBlock>
</Grid>
Here I am not showing the code which can update the value every second. I just want to clarify that this is not the right situation to use Dependency Property.
Of course you can use Dependency Property to do this. But Dependency Object and Dependency Property can offer you some extension functionality such as Data Binding. But it doesn't mean that you need to use a Dependency Object or Dependency Property as the source of the Data Binding.
I am trying to create a composite DataContext for a UserControl. Basically I have a control which has Order and Package properties and I wanted to create the composite object representing this datasource in XAML rather than in code.
This is how I am trying to display the UserControl (and create the DataContext):
<views:PackageDetailsControl>
<views:PackageDetailsControl.DataContext>
<vm:OrderPackagePair Package="{Binding Package, Mode=OneWay}"
Order="{Binding Order, Mode=OneWay}"/>
</views:PackageDetailsControl.DataContext>
</views:PackageDetailsControl>
The OrderPackagePair object is a simple dependency object that is created in XAML :
public class OrderPackagePair : DependencyObject
{
public OrderDetails Order
{
get { return (OrderDetails)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register("Order", typeof(OrderDetails), typeof(OrderPackagePair), new UIPropertyMetadata(null));
public PackageInfo Package
{
get { return (PackageInfo)GetValue(PackageProperty); }
set { SetValue(PackageProperty, value); }
}
public static readonly DependencyProperty PackageProperty =
DependencyProperty.Register("Package", typeof(PackageInfo), typeof(OrderPackagePair), new UIPropertyMetadata(null));
}
Order and Package are not bound correctly and are just null.
Yes I know there's probably a better way of doing this - but I cannot understand why this isn't working. Occasionally in Blend it'll work and then go blank again.
This will not work because DependencyObject(OrderPackagePair class) doesn't monitor internal changes of its dependency properties. As OrderPackagePair object remains the same, DataContext considered as unchanged.
On the opposite site, class Freezable is intented to notify subscribers that instance was changed when one of its dependency properties changed.
So, try to declare Freezable instead of DependencyObject as base class of OrderPackagePair.
------------- UPDATE --------
Yes, it works. In order to prove it I've implemented simple example.
Code of OrderPackagePairClass:
public class OrderPackagePair : Freezable
{
public OrderDetails Order
{
get { return (OrderDetails)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register("Order", typeof(OrderDetails), typeof(OrderPackagePair), new UIPropertyMetadata(null));
public PackageInfo Package
{
get { return (PackageInfo)GetValue(PackageProperty); }
set { SetValue(PackageProperty, value); }
}
public static readonly DependencyProperty PackageProperty =
DependencyProperty.Register("Package", typeof(PackageInfo), typeof(OrderPackagePair), new UIPropertyMetadata(null));
protected override Freezable CreateInstanceCore()
{
throw new NotImplementedException();
}
}
XAML:
<Window x:Class="WindowTest.MainWindow"
xmlns:self="clr-namespace:WindowTest"
Name="RootControl">
<StackPanel Margin="10" DataContextChanged="StackPanel_DataContextChanged">
<StackPanel.DataContext>
<self:OrderPackagePair Package="{Binding Path=DataContext.PackageInfo, Mode=OneWay, ElementName=RootControl}"
Order="{Binding Path=DataContext.OrderDetails, Mode=OneWay, ElementName=RootControl}"/>
</StackPanel.DataContext>
<Button Margin="10" Content="Change Package" Click="Button_Click"/>
</StackPanel>
</Window>
And code behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private OrderDetails _orderDetails;
public OrderDetails OrderDetails
{
get
{
return this._orderDetails;
}
set
{
this._orderDetails = value;
this.OnPropertyChanged("OrderDetails");
}
}
private PackageInfo _packageInfo;
public PackageInfo PackageInfo
{
get
{
return this._packageInfo;
}
set
{
this._packageInfo = value;
this.OnPropertyChanged("PackageInfo");
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.PackageInfo = new PackageInfo(DateTime.Now.ToString());
}
private void StackPanel_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Trace.WriteLine("StackPanel.DataContext changed");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var safeEvent = this.PropertyChanged;
if (safeEvent != null)
{
safeEvent(this, new PropertyChangedEventArgs(name));
}
}
}
When you click the button, model changes PackageInfo property (for simplicity model and view are implemented in the same class). Dependency property OrderPackagePair.Package reacts on new value and overwrites its value. Due to Freezable nature, OrderPackagePair notifies all subscribers that it was changed and handler StackPanel_DataContextChanged is called. If you get back to DependencyObject as base class of OrderPackagePair - handler will be never called.
So, I suppose your code doesn't work because of other mistakes. You should carefully work with DataContext. For example, you wrote:
<views:PackageDetailsControl>
<views:PackageDetailsControl.DataContext>
<vm:OrderPackagePair Package="{Binding Package, Mode=OneWay}"
Order="{Binding Order, Mode=OneWay}"/>
</views:PackageDetailsControl.DataContext>
</views:PackageDetailsControl>
and certainly this is one of the problems. Binding expression is oriented on current DataContext. But you set DataContext as OrderPackagePair instance. So you binded OrderPackagePair.Package to OrderPackagePair.Package (I suppose, that your goal is to bind OrderPackagePair.Package to Model.Package). And that's why nothing happened.
In my example in binding expression I explicitly tell to which DataContext I want to bind:
Package="{Binding Path=DataContext.PackageInfo, Mode=OneWay, ElementName=RootControl}"
I have an ObservableCollection of "Layouts" and a "SelectedLocation" DependencyProperty on a Window. The SelectedLocation has a property called "Layout", which is an object containing fields like "Name" etc. I'm trying to bind a combobox to the SelectedLayout but it's not working.
The following does not work, I've tried binding to SelectedItem instead to no avail. I believe it may be something to do with the fact that I'm binding to a subProperty of the SelectedLocation DependencyProperty (though this does implement INotifyPropertyChanged.
<ComboBox Grid.Row="2" Grid.Column="0" x:Name="cboLayout" ItemsSource="{Binding Layouts,ElementName=root}" SelectedValue="{Binding SelectedLocation.Layout.LayoutID,ElementName=root}" DisplayMemberPath="{Binding Name}" SelectedValuePath="LayoutID" />
However, the following works (Also bound to the "SelectedLocation" DP:
<TextBox Grid.Row="4" Grid.Column="1" x:Name="txtName" Text="{Binding SelectedLocation.Name,ElementName=root,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
What type property Layouts has? I suppose something like this this: IEnumerable<Layout>.
But you bind selected value to Layout.LayoutID. So you got situation, when combo box contains Layout objects, and you try to select it by Int identifier. Of course binding engine can't find any Int there.
I have no idea about details of your code, so one thing I could propose: try to reduce your binding expression: SelectedItem="{Binding SelectedLocation.Layout,ElementName=root}.
If no success, provide more code to help me understand what's going on.
====UPDATE====
As I've said, you are obviously doing something wrong. But I am not paranormalist and couldn't guess the reason of your fail (without your code). If you don't want to share your code, I decided to provide simple example in order to demonstrate that everything works. Have a look at code shown below and tell me what is different in your application.
Class Layout which exposes property LayoutId:
public class Layout
{
public Layout(string id)
{
this.LayoutId = id;
}
public string LayoutId
{
get;
private set;
}
public override string ToString()
{
return string.Format("layout #{0}", this.LayoutId);
}
}
Class SelectionLocation which has nested property Layout:
public class SelectedLocation : INotifyPropertyChanged
{
private Layout _layout;
public Layout Layout
{
get
{
return this._layout;
}
set
{
this._layout = value;
this.OnPropertyChanged("Layout");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var safeEvent = this.PropertyChanged;
if (safeEvent != null)
{
safeEvent(this, new PropertyChangedEventArgs(name));
}
}
}
And Window class with dependency properties (actually, in my example StartupView is UserControl, but it doesn't matter):
public partial class StartupView : UserControl
{
public StartupView()
{
InitializeComponent();
this.Layouts = new Layout[] { new Layout("AAA"), new Layout("BBB"), new Layout("CCC") };
this.SelectedLocation = new SelectedLocation();
this.SelectedLocation.Layout = this.Layouts.ElementAt(1);
}
public IEnumerable<Layout> Layouts
{
get
{
return (IEnumerable<Layout>)this.GetValue(StartupView.LayoutsProperty);
}
set
{
this.SetValue(StartupView.LayoutsProperty, value);
}
}
public static readonly DependencyProperty LayoutsProperty =
DependencyProperty.Register("Layouts",
typeof(IEnumerable<Layout>),
typeof(StartupView),
new FrameworkPropertyMetadata(null));
public SelectedLocation SelectedLocation
{
get
{
return (SelectedLocation)this.GetValue(StartupView.SelectedLocationProperty);
}
set
{
this.SetValue(StartupView.SelectedLocationProperty, value);
}
}
public static readonly DependencyProperty SelectedLocationProperty =
DependencyProperty.Register("SelectedLocation",
typeof(SelectedLocation),
typeof(StartupView),
new FrameworkPropertyMetadata(null));
}
XAML of StartupView:
<UserControl x:Class="Test.StartupView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:self="clr-namespace:HandyCopy"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Root">
<WrapPanel>
<ComboBox ItemsSource="{Binding Path=Layouts,ElementName=Root}"
SelectedItem="{Binding Path=SelectedLocation.Layout, ElementName=Root}"/>
</WrapPanel>
</UserControl>
I am just new to WPF.
I have a wpf app and there i simply have a dock panel and inside dock panel i have a textblock.
I want to bind the text property of textblock to my custom object's property but that' not working.
I think i am missing something here but don't know what.
Here is the code snippet.
<TextBlock Text="{Binding Source=myDataSource, Path=ColorName}"/>
</DockPanel>
My custom class.
class MyData
{
public string ColorName { get; set; }
}
and main window constructor..
public partial class MainWindow : Window
{
MyData myDataSource;
public MainWindow()
{
InitializeComponent();
myDataSource = new MyData { ColorName = "Red" };
}
}
myDataSource needs a get and set. You also need to set the dataContext for the window, so it should be-
public partial class MainWindow : Window
{
public MyData MyDataSource { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
MyDataSource = new MyData { ColorName = "Red" };
}
}
public class MyData
{
public string ColorName { get; set; }
}
and xaml code should be -
<TextBlock Text="{Binding MyDataSource.ColorName}"/>
edit Sorry got this wrong I've changed to the correct code
What you bind to needs to be a
public property. (Your data-object needs to satisfy that condition as well)
Unless you set the property before
InitializeComponent() the binding
will might not update depending on your binding.
If you set it again at any point in
time after the initilization and want the binding to be updated you
should implement
INotifyPropertyChanged or work
with dependency properties.
Since the data is in your window you might need to access it over that:
{Binding ElementName=window, Path=myDataSource.ColorName}
If you only want to bind to MyData, don't set window as its own DataContext. Istead, set the data you're binding to. This way it's more clear what is data, and what is view.
Also, lose the Source on Binding, you won't generally need it.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyData { ColorName = "Red" };
}
}
public class MyData
{
public string ColorName { get; set; }
}
XAML:
<TextBlock Text="{Binding ColorName}"/>