I'm sure this is a pretty common scenario, and I would like to know how MVVM developers tackle this.
I have a ViewModel that is instantiated on demand, and persists until it is explicitly removed by the user. It's corresponding View is loaded onto the UI on demand. The View is able to unload and it's ViewModel may still exists in the application.
In my scenario, I have a ListBox of preset colors in the View(by setting it's ItemsSource to a Xaml-defined ObservableCollection of SolidColorBrush).
I bound the ListBox's SelectedItem property to a property in the ViewModel so that when the View is to be loaded again, the SelectedItem correctly shows the last selected item in the ListBox and also when the user selects a different color, the VM will handle the change.
My question is, how would u set the default value, say the third item in the ObservableCollection of SolidColorBrush to the ViewModel when the View is first loaded?
Usually I set the defaults in the Constructor, unless the defaults may take some time to load, in which case I'll set call a method to set it in the getter for the bound property.
The reason for this is to simplify maintenance. If I am looking for where I set the default value so I can view or change it, the first place I check is the constructor. It's easier to find than scrolling through the properties, and is known to contain initialization logic.
MyViewModel()
{
// Set defaults
SelectedColor = Brushes.Red;
}
For properties that may take longer to load, I use a method that is called in the getter for the same reason. Typically all my properties and their getters/setters are hidden in a region, and I find it much easier to find a method called LoadColors() in my class than finding the Colors properties in the huge list of properties I have. Also, it's reusable, so if I need to do something like reset the value, its easy to do so without repeating my code.
ObservableCollection<SolidColorBrush> Colors
{
get
{
if (_colors == null)
LoadColors();
return _colors;
}
set { ... }
}
void LoadColors()
{
// Initialization logic here
}
You can also set the default in your XAML by using the FallbackValue of the binding, however this usually only makes sense when there is a possibility of the binding's DataContext not existing when the binding is evaluated.
<!-- You may have to look up the exact syntax for Brushes.Red -->
<ListBox SelectedItem="{Binding SelectedColor, FallbackValue=Red}" />
And last but not least, you can always resort to code-behind the view to execute view-specific logic like your example. For example,
void ComboBox_Loaded(object obj, EventArgs e)
{
if (MyComboBox.SelectedIndex == -1)
MyComboBox.SelectedIndex = 2;
}
I believe you error is in your implementation. The reason to have MVVM is to have a "separation of concerns". That makes your view just an implementation, that can get switched or updated out if/when the need arises. Once you start putting stuff in your view that is part of the application logic, you are traveling down a path of a maintenance headache, and then spaghetti code can quickly ensue.
Some people say, "Don't put any code in your view", I agree 99% of the time. I say "Don't put any domain/application/business logic in your view."
Whenever you're trying to put some code into your view ask yourself "If I switched from WPF to another framework would my app still work?" If the answer is no, then modify your ViewModel to incorporate what you were trying to put in your view.
First read Jose Answer if your answer to his question will be "yes" try this
XAML
<Window x:Class="icube.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" Loaded="Window_Loaded">
<Grid>
<ListBox Name="myLB" Margin="0,0,445,291"
SelectedItem="{Binding mySelectedItem, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding mySelectedIndex,Mode=OneTime}" >
<ListBox.ItemTemplate>
<!-- or what ever yourTemplate will be-->
<DataTemplate DataType="{x:Type SolidColorBrush}">
<Rectangle Width="20" Height="20" Fill="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code-behind
using System.Windows;
using System.Windows.Media;
using System.Collections.ObjectModel;
namespace icube
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var obcoll = new ObservableCollection<SolidColorBrush>();
obcoll.Add(Brushes.Red);
obcoll.Add(Brushes.Green);
obcoll.Add(Brushes.Yellow);
obcoll.Add(Brushes.Blue);
obcoll.Add(Brushes.Orange);
myLB.ItemsSource = obcoll;
DataContext = new myClass();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//importent if you want to see your selected Element
myLB.ScrollIntoView(myLB.SelectedItem);
}
}
}
myClass
public class myClass
{
private SolidColorBrush _mySelectedItem = new SolidColorBrush();
public SolidColorBrush mySelectedItem
{
get { return _mySelectedItem; }
set { _mySelectedItem = value; }
}
public int mySelectedIndex
{
get { return 4; }
}
}
summary
like you can see the default SelectedItem get setted by the SelectedIndex which is bounded per OneTime Mode to mySelectedIndex. I also show here how you could get it IntoView
i how this will help somebody.
typically In MVVM your ObservableCollection of SolidBrushColor should be defined as a property in ViewModal. and this property should be bound to your ListBox. Now, to set default value you can have it in your ViewModal's constructor
If the property bound to SelectedItem is declared as dependency property then you can set the default value of the dependency propery to whatever you want.
public static readonly DependencyProperty SelectedColorProperty
= DependencyProperty.Register("SelectedColor", typeof(SolidBrushColor ),
typeof(<yourviewmodel>),
new UIPropertyMetadata(GetDefaultColor()));
Where GetDefaultColor() is a static method in your ViewModel,
which will return the required color from your ObservableCollection .
Related
I am trying to learn MVVM and have come across a weird snag. I have a main menu with a drawer control that comes out and shows a menu:
In the main window where this drawer is, I have a ContentControl where I set its content with a Binding.
<ContentControl x:Name="MainWindowContentControl" Content="{Binding Path=WindowContent}"/>
This window's binding is set to a view model.
<Window.DataContext>
<viewmodels:MainWindowViewModel/>
</Window.DataContext>
and here is the ViewModel:
MainWindowViewModel.cs
public class MainWindowViewModel: ViewModelBase
{
private object _content;
public object WindowContent
{
get { return _content; }
set
{
_content = value;
RaisePropertyChanged(nameof(WindowContent));
}
}
public ICommand SetWindowContent { get; set; }
public MainWindowViewModel()
{
SetWindowContent = new ChangeWindowContentCommand(this);
}
}
So far up to this point, everything works fine. So for example, if I click "Recovery Operations", I get this:
RecoveryOperationsView.xaml
In "RecoveryOperationsView.xaml" (which is a UserControl) I also reference the view model from above like so..
<UserControl.DataContext>
<viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
and have a button to call the command to change the Content property of the ContentControl from the main window..
<Button Grid.Row="2" Content="Restore Database" Width="150" Style="{StaticResource MaterialDesignFlatButton}" Command="{Binding SetWindowContent}" CommandParameter="DatabaseRecovery" >
In my class to process the commands, I change the content based off of the passed parameter using a switch statement like so
ChangeWindowContentCommand.cs
public class ChangeWindowContentCommand : ICommand
{
private MainWindowViewModel viewModel;
public ChangeWindowContentCommand(MainWindowViewModel vm)
{
this.viewModel = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
switch (parameter)
{
case "Home":
viewModel.WindowContent = new HomeView();
break;
case "RecoveryOps":
viewModel.WindowContent = new RecoveryOperationsView();
break;
case "DatabaseRecovery":
viewModel.WindowContent = new DatabaseRestoreView();
break;
}
}
}
However, this is where I get lost... If I click something within this new window, say "Restore Database" and inspect it with a breakpoint, I can see the property being changed but the actual ContentControl Content property doesnt change to the new UserControl I made... I can change the content with anything in the drawer, but if I try to click a button in the hosted Content of the ContentControl nothing changes. What am I missing?
It's hard to be 100% sure without having your project to test with, but I am fairly confident that at least one of the issues is that your UserControl and your MainWindow use different instances of the MainWindowViewModel. You do not need to instantiate the VM for the user control, as it will inherit the DataContext from the MainWindow. The way it works in WPF is that if any given UIElement does not have theDataContext assigned explicitly, it will inherit it from the first element up the logical tree that does has one assigned.
So, just delete this code, and it should solve at least that issue.
<UserControl.DataContext>
<viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
And since you're learning WPF, I feel obligated to provide a couple other tips. Even though you're using a ViewModel, you are still mixing UI and logic by creating a very specific implementation of ICommand and assigning a UI element through your ViewModel. This breaks the MVVM pattern. I know MVVM takes a little time to understand, but once you do, it is very easy to use and maintain.
To solve your problem, I would suggest creating View Models for each of your user controls. Please see this answer, where I go into quite a bit of detail on the implementation.
For switching the different views, you have a couple of options. You can either use a TabControl, or if you want to use a command, you can have a single ContentControl bound to a property of MainWindowViewModel that is of type ViewModelBase. Let's call it CurrentViewModel. Then when the command fires, you assign the view model of the desired user control to that bound property. You will also need to utilize implicit data templates. The basic idea is that you create a template for each of the user control VM types, which would just contains an instance of the Views. When you assign the user control VM to the CurrentViewModel property, the binding will find those data templates and render the user control. For example:
<Window.Resources>
<DataTemplate DataType = "{x:Type viewmodels:RecoveryOperationsViewModel}">
<views:RecoveryOperationsView/>
</DataTemplate>
<!-- Now add a template for each of the views-->
</Window.Resources>
<ContentControl x:Name="MainWindowContentControl" Content="{Binding CurrentViewModel}"/>
See how this approach keeps UI and logic at an arm's length?
And lastly, consider creating a very generic implementation of ICommand to use in all your ViewModels rather than many specific implementations. I think most WPF programmers have more or less this exact RelayCommand implementation in their arsenal.
[Edit]: I figured out how to do this on my own. I posted my solution in the hope that it will save someone else a few days of Googling. If you are a WPF guru, please look at my solution and let me know if there is a better / more elegant / more efficient way to do this. In particular, I am interested in knowing what I don't know... how is this solution going to screw me down the road? The problem really boils down to exposing inner control properties.
Problem:
I am creating some code to auto-generate a data-bound GUI in WPF for an XML file. I have an xsd file that can help me determine the node types, etc. Simple Key/Value elements are easy.
When I parse this element:
<Key>value</Key>
I can create a new 'KeyValueControl' and set the DataContext to this element. The KeyValueControl is defined as a UserControl and just has some simple bindings on it. It works great for any simple XElement.
The XAML inside this control looks like this:
<Label Content={Binding Path=Name} />
<TextBox Text={Binding Path=Value} />
The result is a line that has a label with the element name and a text box with the value that I can edit.
Now, there are times where I need to display lookup values instead of the actual value. I would like to create a 'KeyValueComboBox' similar to the above KeyValueControl but be able to specify (based on information in the file) the ItemsSource, DisplayMemberPath, and ValueMemberPath. The 'DisplayMemberPath' and 'ValueMemberPath' bindings would be the same as the KeyValueControl.
I don't know if a standard user control can handle this, or if I need to inherit from Selector.
The XAML in the control would look something like this:
<Label Content={Binding Path=Name} />
<ComboBox SelectedValue={Binding Path=Value}
ItemsSource={Binding [BOUND TO THE ItemsSource PROPERTY OF THIS CUSTOM CONTROL]
DisplayMemberPath={Binding [BOUND TO THE DisplayMemberPath OF THIS CUSTOM CONTROL]
SelectedValuePath={Binding [BOUND TO THE SelectedValuePath OF THIS CUSTOM CONTROL]/>
In my code, I would then do something like this (assuming that this node is a 'Thing' and needs to display a list of Things so the user can select the ID:
var myBoundComboBox = new KeyValueComboBox();
myBoundComboBox.ItemsSource = getThingsList();
myBoundComboBox.DisplayMemberPath = "ThingName";
myBoundComboBox.ValueMemberPath = "ThingID"
myBoundComboBox.DataContext = thisXElement;
...
myStackPanel.Children.Add(myBoundComboBox)
So my questions are:
1) Should I inherit my KeyValueComboBox from Control or Selector?
2) If I should inherit from Control, how do I expose the inner Combo Box's ItemsSource, DisplayMemberPath, and ValueMemberPath for binding?
3) If I need to inherit from Selector, can someone provide a small example of how I might get started with that? Again, I'm new to WPF so a nice, simple example would really help if that's the road I need to take.
I ended up figuring how how to do this on my own. I'm posting the answer here so that others can see a solution that works, and maybe a WPF guru will come by and show me a better/more elegant way to do this.
So, the answer ended up being #2. Exposing the inner properties turns out to be the right answer. Setting it up is actually pretty easy.. once you know how to do it. There aren't many complete examples of this (that I could find), so hopefully this one will help someone else that runs into this problem.
ComboBoxWithLabel.xaml.cs
The important thing in this file is the use of DependencyProperties. Note that all we're doing right now is just exposing the properties (LabelContent and ItemsSource). The XAML will take care of wiring the internal control's properties to these external properties.
namespace BoundComboBoxExample
{
/// <summary>
/// Interaction logic for ComboBoxWithLabel.xaml
/// </summary>
public partial class ComboBoxWithLabel : UserControl
{
// Declare ItemsSource and Register as an Owner of ComboBox.ItemsSource
// the ComboBoxWithLabel.xaml will bind the ComboBox.ItemsSource to this
// property
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
ComboBox.ItemsSourceProperty.AddOwner(typeof(ComboBoxWithLabel));
// Declare a new LabelContent property that can be bound as well
// The ComboBoxWithLable.xaml will bind the Label's content to this
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set { SetValue(LabelContentProperty, value); }
}
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(ComboBoxWithLabel));
public ComboBoxWithLabel()
{
InitializeComponent();
}
}
}
ComboBoxWithLabel.xaml
The XAML is pretty straightforward, with the exception of the bindings on the Label and the ComboBox ItemsSource. I found that the easiest way to get these bindings right is to declare the properties in the .cs file (as above) and then use the VS2010 designer to setup the binding source from the properties pane. Essentially, this is the only way I know of to bind an inner control's properties to the base control. If there's a better way to do it, please let me know.
<UserControl x:Class="BoundComboBoxExample.ComboBoxWithLabel"
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"
mc:Ignorable="d"
d:DesignHeight="28" d:DesignWidth="453" xmlns:my="clr-namespace:BoundComboBoxExample">
<Grid>
<DockPanel LastChildFill="True">
<!-- This will bind the Content property on the label to the 'LabelContent'
property on this control-->
<Label Content="{Binding Path=LabelContent,
RelativeSource={RelativeSource FindAncestor,
AncestorType=my:ComboBoxWithLabel,
AncestorLevel=1}}"
Width="100"
HorizontalAlignment="Left"/>
<!-- This will bind the ItemsSource of the ComboBox to this
control's ItemsSource property -->
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=my:ComboBoxWithLabel,
AncestorLevel=1},
Path=ItemsSource}"></ComboBox>
<!-- you can do the same thing with SelectedValuePath,
DisplayMemberPath, etc, but this illustrates the technique -->
</DockPanel>
</Grid>
</UserControl>
MainWindow.xaml
The XAML to use this is not interesting at all.. which is exactly what I wanted. You can set the ItemsSource and the LabelContent via all the standard WPF techniques.
<Window x:Class="BoundComboBoxExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="86" Width="464" xmlns:my="clr-namespace:BoundComboBoxExample"
Loaded="Window_Loaded">
<Window.Resources>
<ObjectDataProvider x:Key="LookupValues" />
</Window.Resources>
<Grid>
<my:ComboBoxWithLabel LabelContent="Foo"
ItemsSource="{Binding Source={StaticResource LookupValues}}"
HorizontalAlignment="Left"
Margin="12,12,0,0"
x:Name="comboBoxWithLabel1"
VerticalAlignment="Top"
Height="23"
Width="418" />
</Grid>
</Window>
For Completeness Sake, here is the MainWindow.xaml.cs
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
((ObjectDataProvider)FindResource("LookupValues")).ObjectInstance =
(from i in Enumerable.Range(0, 5)
select string.Format("Bar {0}", i)).ToArray();
}
}
I tried your solution but it fails for me. It does not pass the value over to inner control at all. What I did is declaration of same dependency properties in outer control and bound inner to outer like that:
// Declare IsReadOnly property and Register as an Owner of TimePicker (base InputBase).IsReadOnly the TimePickerEx.xaml will bind the TimePicker.IsReadOnly to this property
// does not work: public static readonly DependencyProperty IsReadOnlyProperty = InputBase.IsReadOnlyProperty.AddOwner(typeof(TimePickerEx));
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof (bool), typeof (TimePickerEx), new PropertyMetadata(default(bool)));
public bool IsReadOnly
{
get { return (bool) GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
Than in xaml:
<UserControl x:Class="CBRControls.TimePickerEx" x:Name="TimePickerExControl"
...
>
<xctk:TimePicker x:Name="Picker"
IsReadOnly="{Binding ElementName=TimePickerExControl, Path=IsReadOnly}"
...
/>
</UserControl>
Hello im new to making apps with WPF and XAML in Visual Studio. So I have a grid I want to change its properties in the code.
My Grid's properties:
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286">
How I have been trying to change its properties
this.usersPan.SetValue(Grid.WidthProperty, PAN_SIZE);
usersPan.SetValue(Grid.WidthProperty, PAN_SIZE);
usersPan.Width = 0;
usersPan.Visibility = Visibility.Collapsed;
When I try to do that^ it says null reference for userPan
Thanks
Noooooooo, Don't ever do that. Make a ViewModel that is bound to the Grid's Width property, and then just change the value.
My suspicion is that you do not need this at all. Have a look into containers, and how to position them.
In all of this years, there have been rare occasions I needed to do that and I suspect you do not need to. Tell me what you are doing.
EDIT:
You have a VM which needs to implement the NotifyPropertyChanged interface (I won't do that here, there are plenty of examples on hoew to do that)
public class MainVM
{
public ObservableCollection<TabVM> TabsVms {get;set;}
public int SelectedIndex {get;set}
}
bound to the control
<TabControl DataContext={TabsVMs} SelectedIndex="{Binding SelectedIndex}">
...
</TabControl>
And in runtime you create a couple of Tabs
var TabsVMs = new ObservableCollection<TabVM>();
TabsVMs.add(new TabVM());
TabsVMs.add(new TabVM());
TabsVMs.add(new TabVM());
Then in runtime you change the value of the index.
MainVm.SelectedIndex = 1
and the the coresponding tab will become selected.
EDIT:
I can also recommend you to use Fody for the MVVM notification.
Also, when it comes to bindings, I can recommend you to use WPF inspector. a handy little tool
The best way to write WPF programs is to use the MVVM (Model-View-View Model) design pattern. There are two (2) ideas behind MVVM:
Write as little code as possible in the view's code-behind and put all of the logic in the View Model object, using WPF's data binding feature to connect the properties of the View Model object to the view's controls.
Separate the logic from the display so you can replace the view with some other construct without having to change the logic.
MVVM is a huge topic on its own. There are lots of articles about it, and frameworks that you can use to build your program. Check out MVVM Light, for example.
Don't know exactly why Grid is invisible in code-behind, but You can access it's properties using events (but don't think it is perfect solution).
For example add to your grid event Loaded
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286"
Loaded="FrameworkElement_OnLoaded">
and then from code-behind you can access grid in next way:
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var grid = sender as Grid;
if (grid != null)
{
grid.Width = 0;
}
}
Better solution :
Add some boolean property to your ViewModel like public bool IsGridVisible{get;set;}
And bind it to your Grid
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286"
Visibility="{Binding Path=IsGridVisible, Converter={StaticResource BoolToVis}">
where BoolToVis is converter which converts true to Visible and false to Hidden. You can define it in App.xaml like :
<BooleanToVisibilityConverter x:Key="BoolToVis" />
I was able to do something like this so I can change properties outside of an event.
private Grid userGrid;
private void onUserGridLoaded(object sender, RoutedEventArgs e)
{
userGrid = sender as Grid;
}
I'm currently experimenting with the new compiled bindings and have reached (again) a point where I'm missing a pice in the puzzle: why do I have to call Bindings.Update? Until now, I thought implementing INotifyPropertyChanged is enough?
In my example, the GUI is only displaying correct values, if I call this mysterious method (which is autogenerated by the compiled bindings).
I am using a user control with the following (here simplified) xaml syntax:
<UserControl>
<TextBlock Text="x:Bind TextValue"/>
</UserControl>
where TextValue is a simple dependency property of this user control. In a page, I'm using this control as:
<Page>
<SampleControl TextValue="{x:Bind ViewModel.Instance.Name}"/>
</Page>
where:
ViewModel is a standard propery which is set before InitializeComponent() is run
Instance is a simple object implementing INotifyPropertyChanged
After loading Instance, i raise a property changed event for Instance. I can even debug to the line, where the depency property TextValue of user control gets the correct value -- but nothing is displayed. Only if I call Bindings.Update(), the value is displayed. What am I missing here?
Update
I doesn`t work with {x:Bind ... Mode=OneWay} either.
More code
Person.cs:
using System.ComponentModel;
using System.Threading.Tasks;
namespace App1 {
public class Person : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name { get {
return this.name;
}
set {
name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
public class ViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private Person instance;
public Person Instance {
get {
return instance;
}
set {
instance = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Instance"));
}
}
public Task Load() {
return Task.Delay(1000).ContinueWith((t) => {
var person = new Person() { Name = "Sample Person" };
this.Instance = person;
});
}
}
}
SampleControl.cs:
<UserControl
x:Class="App1.SampleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="100">
<TextBlock Text="{x:Bind TextValue, Mode=OneWay}"/>
</UserControl>
SampleControl.xaml.cs:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace App1 {
public sealed partial class SampleControl : UserControl {
public SampleControl() {
this.InitializeComponent();
}
public string TextValue {
get { return (string)GetValue(TextValueProperty); }
set { SetValue(TextValueProperty, value); }
}
public static readonly DependencyProperty TextValueProperty =
DependencyProperty.Register("TextValue", typeof(string), typeof(SampleControl), new PropertyMetadata(string.Empty));
}
}
MainPage.xaml:
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:SampleControl TextValue="{x:Bind ViewModel.Instance.Name, Mode=OneWay}"/>
</StackPanel>
</Page>
MainPage.xaml.cs:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace App1 {
public sealed partial class MainPage : Page
{
public MainPage()
{
this.DataContext = new ViewModel();
this.Loaded += MainPage_Loaded;
this.InitializeComponent();
}
public ViewModel ViewModel {
get {
return DataContext as ViewModel;
}
}
private void MainPage_Loaded(object sender, RoutedEventArgs e) {
ViewModel.Load();
Bindings.Update(); /* <<<<< Why ????? */
}
}
}
One more update
I updated the Load method to use task (see the code above)!
Sometimes the data you want to show is not available (like returned from the server or database) until several seconds after your page has loaded and rendered. This is especially true if you call your data in a background/async process that frees up your UI to render without a hang.
Make sense so far?
Now create a binding; let's say something like this:
<TextBlock Text="{x:Bind ViewModel.User.FirstName}" />
The value of your ViewModel property in your code-behind will have a real value and will bind just fine. Your User, on the other hand, will not have a value because it is not returned from the server yet. As a result neither that nor the FirstName property of the User can be displayed, right?
Then your data is updated.
You would think that your binding would automatically update when you set the value of the User object to a real object. Especially if you took the time to make it a INotifyPropertyChanged property, right? That would be true with traditional {Binding} because the default binding mode is OneWay.
What is the OneWay binding mode?
The OneWay binding mode means that you can update your backend model properties that implement INotifyPropertyChanged and the UI element bound to that property will reflect the data/value change. It's wonderful.
Why does it not work?
It is NOT because {x:Bind} does not support Mode=OneWay, it is because it defaults to Mode=OneTime. To recap, traditional {Binding} defaults to Mode=OneWay and compiled {x:Bind} defaults to Mode=OneTime.
What is the OneTime binding mode?
The OneTime binding mode means that you bind to the underlying model only once, at the time of load/render of the UI element with the binding. This means that if your underlying data is not yet available, it cannot display that data and once the data is available it will not display that data. Why? Because OneTime does not monitor INotifyPropertyChanged. It only reads when it loads.
Modes (from MSDN): For OneWay and TwoWay bindings, dynamic changes to the source don't automatically propagate to the target without providing some support from the source. You must implement the INotifyPropertyChanged interface on the source object so that the source can report changes through events that the binding engine listens for. For C# or Microsoft Visual Basic, implement System.ComponentModel.INotifyPropertyChanged. For Visual C++ component extensions (C++/CX), implement Windows::UI::Xaml::Data::INotifyPropertyChanged.
How to solve this problem?
There are a few ways. The first and easiest is to change your binding from ="{x:Bind ViewModel.User.FirstName} to ="{x:Bind ViewModel.User.FirstName, Mode=OneWay}. Doing this will monitor for INotifyPropertyChanged events.
This is the right time to warn you that using OneTime by default is one of the many ways {x:Bind} tries to improve performance of binding. That's because OneTime is the fastest possible with the least memory reqs. Changing your binding to OneWay undermines this, but it might be necessary for your app.
The other way to fix this problem and still maintain the performance benefits that come out of the box with {x:Bind} is to call Bindings.Update(); after your view model has completely prepared your data for presenting. This is easy if your work is async - but, like your sample above, if you can't be sure a timer might be your only viable option.
That sucks of course because a timer implies clock time, and on slow devices like a phone, that clock time might not properly apply. This is something every developer will have to work out specific to their app - that is to say, when is your data fully loaded and ready?
I hope this explains what is happening.
Best of luck!
While "traditional" bindings default to "one-way" (or two-ways in some case), compiled bindings default to "one-time". Just change the mode when setting the binding:
<TextBlock Text="{x:Bind TextValue, Mode=OneWay}" />
Finally I found the bug myself: I was using an task-based operation to load my view model, which resulted in setting the dependency property by the incorrect thread (I think). It works if I set the Instance property via the dispatcher.
public Task Load() {
return Task.Delay(1000).ContinueWith((t) => {
var person = new Person() { Name = "Sample Person" };
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => {
this.Instance = person;
});
});
}
But there was no exception, just the gui displaying no value!
First of all,the default binding mode of x:Bind is OneTime, you need to changed it to OneWay as the answer said above to make it work if you call RaisePropertyChanged method.
It seems like something has wrong with your code of data binding. PLEASE paste all the involved code to let us see the source of this issue.
I'm curious how this works, because I have a MainViewModel, which has Property say called SubViewModel which has a Property of ObservableCollection (we'll call it Property1.)
I've implemented INotifyChangedProperty on everything.
My Main Window
<Window ..
DataContext="{Binding MainViewModel}" />
...
<StackPanel DataContext="{Binding SubViewModel}">
<local:SomeControl DataContext="{Binding}" />
</StackPanel>
</Window>
And my UserControl
<UserControl Name="SomeControl">
<DataGrid Name="MyDataGrid" ItemSource="{Binding Property1, Mode=TwoWay}" CurrentCellChanged="TestMethod" />
...
</UserControl>
In my test method, just as a test to figure out why the changes are not propegating up to the main view model I do something like this
private void TestMethod()
{
var vm = this.DataContext as SubViewModel;
var itemSourceObservableCollection = MyDataGrid.ItemsSource as ObservableCollection<MyType>;
//I thought vm.Property1 would be equal to itemSourceObservableCollection
//but they are not, itemSourceObservableCollection shows the changes I've made
//vm.Property1 has not reflected any changes made, even though I though they were the same item
}
So I figured out that ItemSource must create a copy of the item you bind it to? I'm stuck here, how do manually notify the viewModel that this property has changed and it needs to update? I thought that was INotifyPropertyChanged's job?
I think part of my problem is I lack the understanding of how this kinda works internally. If anyone can point to a good blog post, or documentation to help me understand why my code isn't working the way I expected, that would be great.
1) No copy is made.
2) ObservableCollection will propogate changes made to the collection, not the items within the collection. So you'll see additions, deletions etc. but NOT property changes to items within the collection.
3) If you want to see changes made to individual items in the ObservableCollection, you need to implement INotifyPropertyChanged on those items.
There's actually TWO different issues here. What happens internally when you bind to a collection? AND why changes on the user surface are not propagated back to your View Model. Based upon what you wrote, the two issues are not connected, but let's take them one at a time...
For the first issue... When you bind a collection, the WPF binding engine creates a "CollectionView" class that mediates between your object store and the logical tree. You can, if needed, get a copy of the the "CollectionView" using a static method on CollectionViewSource...
var cvs = CollectionViewSource.GetDefaultView(MyCollectionOfThings);
There are several interesting properties in the result, and some of them contain write accessors which allow you to directory modify the CollectionView.
For the second issue... The business classes in your SubViewModel need to inherit from INotifyPropertyChanged such that changes are 'announced' via the WPF binding engine. Your VM should be a publisher, but can also be a subscriber. A property that participates in the INotifyPropertyChanged plumbing gets declared like this...
private string _name;
[Description("Name of the driver")]
public string Name
{
[DebuggerStepThrough]
get { return _name; }
[DebuggerStepThrough]
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
This code publishes changes, but can also subscribe to changes made on the user surface by setting the appropriate attributes in your Xaml.
Background reading: What is a CollectionView?
Also, Similar question