I've been playing around with WPF for the first time, specifically using a ListView that I want to bind to a ObservableCollection that is a property on the code-behind page. Right now I'm just trying to get a feel for how things work so I've tried keeping this simple. Unfortunately I don't quite see where I'm going wrong with this.
My code-behind page has a property that looks like this:
public ObservableCollection<Code> Code { get; set; }
I have a button on the form that queries and populates the Code property.
The Code class is a simple POCO class:
public class Code
{
public string Line { get; set; }
}
I have added a namespace to the XAML window:
<Window x:Class="SampleWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleWPF"
Title="MainWindow" Height="350" Width="525"
>
And the ListView looks like this:
<DockPanel Height="311" HorizontalAlignment="Left" Name="dockPanel1"
VerticalAlignment="Top" Width="182">
<ListView Name="lstCode"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window, AncestorLevel=1}, Path=Code}"
DisplayMemberPath="Line">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Line}" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
I have also attempted to set the DataContext in the code behind contructor, with no luck, ex.:
this.DataContext = this;
EDIT: Moving this line to after the line of code that creates the collection fixed things (along with the other changes suggested).
And I also tried to explicitly set the ItemsSource in code (in my click handler):
this.lstCode.ItemsSource = this.Code;
I've looked at a number of examples but I'm still missing something here (not really a surprise).
Uh, you're trying to do something simple with some terrible magic ;)
Your binding should look like {Binding Path=Code}. To make this work you should also set DataContext to this, just like you wrote. This should give you simplest binding. Magic with finding ancestors is not necessary in here.
In advanced applications you should rather use Model - View - ViewModel pattern and set data context to ViewModel object rather than to this, but just for testing and trying WPF out, this approach should be ok.
Here is some sample:
<Window x:Class="binding_test.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>
<ListView ItemsSource="{Binding Path=Code}" />
</Grid>
And code behind:
using System.Collections.ObjectModel;
using System.Windows;
namespace binding_test
{
public partial class MainWindow : Window
{
public ObservableCollection<int> Code { get; set; }
public MainWindow()
{
InitializeComponent();
Code = new ObservableCollection<int>();
Code.Add(1);
this.DataContext = this;
}
}
}
And here is how you should create listview for your sample. You have special class and you probably don't want to display ToString() result on each object. To display element any way you could imagine, you should use data template and there create controls and bind them to properties of element, that was in list you've bind ListView.
<ListView ItemsSource="{Binding Code}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Line}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Related
I want to update the text of an item in a combobox when the item is saved.
Although the entity that I use as source implements the notify property changed event, it is not update. I have read the solution it is to use a data templeate instead of DisplayMemberPath.
So I am using this:
<DataTemplate x:Key="TipoComponenteTemplate"
DataType="{x:Type clases:TiposComponentesUI}">
<TextBlock Text="{Binding TipoComponente}"/>
</DataTemplate>
<ComboBox Name="cmbTiposComponentes" Width="150"
Text="{Binding TiposComponentesTexto, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding TiposComponentes}"
ItemTemplate="{StaticResource ResourceKey=TipoComponenteTemplate}"
SelectedItem="{Binding TiposComponentesSelectedItem}">
That works partially, in the way when I open the combobox, the items show the correct text, but when I select one of them, in the textbox of the combobox it is shown the name of the class instead of the data in the property TipoComponente that is defined in the data template.
So I would like to know how I could show the same text in the textbox than the text that is shown in the items.
Thanks.
I've reproduced this scenario and both the the ItemTemplate and the DisplayMemberPath solutions work fine for me, as long as the ComboBox's IsEditable property is set to False.
When IsEditable="False"
Here's what I have:
Book.cs
using CommunityToolkit.Mvvm.ComponentModel;
namespace WpfApp2;
public partial class Book : ObservableObject
{
[ObservableProperty]
private string? _author;
[ObservableProperty]
private string? _title;
}
MainViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.ObjectModel;
namespace WpfApp2;
public partial class MainViewModel : ObservableObject
{
public ObservableCollection<Book> Books { get; } = new();
[ObservableProperty]
private Book? _selectedBook;
public MainViewModel()
{
Books.Add(new Book { Author = "Jim Weasley", Title = "The mystery of eggplants" });
Books.Add(new Book { Author = "Ryan Reynolds", Title = "Doplhins and how to beat them" });
Books.Add(new Book { Author = "Sarah O'Connel", Title = "What is djent?" });
}
[RelayCommand]
private void UpdateRandomBookTitles()
{
foreach (var book in Books)
{
book.Title = Guid.NewGuid().ToString();
}
}
}
MainWindow.xaml.cs
<Window
x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="BookDisplayTemplate" DataType="{x:Type local:Book}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Command="{Binding UpdateRandomBookTitlesCommand}" Content="Randomize book titles" />
<ComboBox
ItemTemplate="{StaticResource BookDisplayTemplate}"
ItemsSource="{Binding Books}"
SelectedItem="{Binding SelectedBook}" />
<!-- This will work as well -->
<!-- <ComboBox
DisplayMemberPath="Title"
ItemsSource="{Binding Books}"
SelectedItem="{Binding SelectedBook}" />-->
</StackPanel>
</Window>
After you've selected a book, changing it's Title will be reflected in ComboBox's Text as well.
The problem: when IsEditable="True"
Once you set IsEditable="True", this stops working and I believe it's intentional. When IsEditable="True", the ComboBox's Text is only update when:
an item is selected
user types text manually
Changing the SelectedBook's Title does nothing here, my guess is so that it doesn't overwrite user input. Only selected another item will do that.
I have been unable to find a clean, simple, example of how to correctly implement a usercontrol with WPF that has a DependencyProperty within the MVVM framework. My code below fails whenever I assign the usercontrol a DataContext.
I am trying to:
Set the DependencyProperty from the calling ItemsControl , and
Make the value of that DependencyProperty available to the ViewModel of the called usercontrol.
I still have a lot to learn and sincerely appreciate any help.
This is the ItemsControl in the topmost usercontrol that is making the call to the InkStringView usercontrol with the DependencyProperty TextInControl (example from another question).
<ItemsControl ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the InkStringView usercontrol with the DependencyProperty.
XAML:
<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView"
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"
x:Name="mainInkStringView"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" />
<TextBlock Grid.Row="1" Text="I am row 1" />
</Grid>
</UserControl>
Code-Behind file:
namespace Nova5.UI.Views.Ink
{
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
this.DataContext = new InkStringViewModel(); <--THIS PREVENTS CORRECT BINDING, WHAT
} --ELSE TO DO?????
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
}
That is one of the many reasons you should never set the DataContext directly from the UserControl itself.
When you do so, you can no longer use any other DataContext with it because the UserControl's DataContext is hardcoded to an instance that only the UserControl has access to, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.
There are two main ways of using UserControls in WPF
A standalone UserControl that can be used anywhere without a specific DataContext being required.
This type of UserControl normally exposes DependencyProperties for any values it needs, and would be used like this:
<v:InkStringView TextInControl="{Binding SomeValue}" />
Typical examples I can think of would be anything generic such as a Calendar control or Popup control.
A UserControl that is meant to be used with a specific Model or ViewModel only.
These UserControls are far more common for me, and is probably what you are looking for in your case. An example of how I would use such a UserControl would be this:
<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
Or more frequently, it would be used with an implicit DataTemplate. An implicit DataTemplate is a DataTemplate with a DataType and no Key, and WPF will automatically use this template anytime it wants to render an object of the specified type.
<Window.Resources>
<DataTemplate DataType="{x:Type m:InkStringViewModel}">
<v:InkStringView />
</DataTemplate>
<Window.Resources>
<!-- Binding to a single ViewModel -->
<ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
<!-- Binding to a collection of ViewModels -->
<ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
No ContentPresenter.ItemTemplate or ItemsControl.ItemTemplate is needed when using this method.
Don't mix these two methods up, it doesn't go well :)
But anyways, to explain your specific problem in a bit more detail
When you create your UserControl like this
<v:InkStringView TextInControl="{Binding text}" />
you are basically saying
var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
vw.DataContext is not specified anywhere in the XAML, so it gets inherited from the parent item, which results in
vw.DataContext = Strings[x];
so your binding that sets TextInControl = vw.DataContext.text is valid and resolves just fine at runtime.
However when you run this in your UserControl constructor
this.DataContext = new InkStringViewModel();
the DataContext is set to a value, so no longer gets automatically inherited from the parent.
So now the code that gets run looks like this:
var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
and naturally, InkStringViewModel does not have a property called text, so the binding fails at runtime.
You're almost there. The problem is that you're creating a ViewModel for your UserControl. This is a smell.
UserControls should look and behave just like any other control, as viewed from the outside. You correctly have exposed properties on the control, and are binding inner controls to these properties. That's all correct.
Where you fail is trying to create a ViewModel for everything. So ditch that stupid InkStringViewModel and let whoever is using the control to bind their view model to it.
If you are tempted to ask "what about the logic in the view model? If I get rid of it I'll have to put code in the codebehind!" I answer, "is it business logic? That shouldn't be embedded in your UserControl anyhow. And MVVM != no codebehind. Use codebehind for your UI logic. It's where it belongs."
Seems like you are mixing the model of the parent view with the model of the UC.
Here is a sample that matches your code:
The MainViewModel:
using System.Collections.Generic;
namespace UCItemsControl
{
public class MyString
{
public string text { get; set; }
}
public class MainViewModel
{
public ObservableCollection<MyString> Strings { get; set; }
public MainViewModel()
{
Strings = new ObservableCollection<MyString>
{
new MyString{ text = "First" },
new MyString{ text = "Second" },
new MyString{ text = "Third" }
};
}
}
}
The MainWindow that uses it:
<Window x:Class="UCItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:UCItemsControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<v:MainViewModel></v:MainViewModel>
</Window.DataContext>
<Grid>
<ItemsControl
ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Your UC (no set of DataContext):
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
}
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
(Your XAML is OK)
With that I can obtain what I guess is the expected result, a list of values:
First
I am row 1
Second
I am row 1
Third
I am row 1
You need to do 2 things here (I'm assuming Strings is an ObservableCollection<string>).
1) Remove this.DataContext = new InkStringViewModel(); from the InkStringView constructor. The DataContext will be one element of the Strings ObservableCollection.
2) Change
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
to
<v:InkStringView TextInControl="{Binding }" />
The xaml you have is looking for a "Text" property on the ItemsControl to bind the value TextInControl to. The xaml I put using the DataContext (which happens to be a string) to bind TextInControl to. If Strings is actually an ObservableCollection with a string Property of SomeProperty that you want to bind to then change it to this instead.
<v:InkStringView TextInControl="{Binding SomeProperty}" />
I'm a little confused about how the data binding works when using these types.
I've read that you can't do the following
public partial class Window1 : Window
{
public ObservableCollection<string> Items { get; private set; }
public Window1()
{
Items = new ObservableCollection<string>() { "A", "B", "C" };
DataContext = this;
InitializeComponent();
}
}
<Window x:Class="WpfApplication25.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ComboBox>
<ComboBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Items}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
</Window>
because CompositeCollection has no notion of datacontext and so anything inside of it using a binding has to set the Source property. Such as the following :
<Window x:Class="WpfApplication25.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<CollectionViewSource x:Key="list" Source="{Binding Items}"/>
</Window.Resources>
<ComboBox Name="k">
<ComboBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource list}}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
</Window>
But how is that working? it sets the source to something, but that something, in this case a CollectionViewSource uses a datacontext (as its not explicitly setting a source).
So because "list" is declared in the resources of Window, does that mean it gets Windows DataContext? In which case, why doesn't the following also work?
<Window x:Class="WpfApplication25.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<Button x:Key="menu" Content="{Binding Items.Count}"/>
</Window.Resources>
<ComboBox Name="k">
<ComboBox.ItemsSource>
<CompositeCollection>
<ContentPresenter Content="{Binding Source={StaticResource menu}}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
</Window>
you are right CompositeCollection has no notion of datacontext so it cant inherit it from its parent.
from MSDN:
CompositeCollection can contain items such as strings, objects, XML nodes, elements, as well as other collections. An ItemsControl uses the data in the CompositeCollection to generate its content according to its ItemTemplate. For more information about using ItemsControl objects to bind to collections, see the Binding to Collections section of the Data Binding Overview.
to your question
But how is that working? it sets the source to something, but that something, in this case a CollectionViewSource uses a DataContext (as its not explicitly setting a source).
I guess you over think it, the Collection DependecyProperty can bind to any IEnumerable type so it doesn't matter how the collection was created as long as its created and implements IEnumerable.
in your case the CVS inherits the DataContext from the Window and then binds to Items.
regarding your second example it doesn't work because the ContentPesenter needs dataContext to work so since it could inherit it, the binding mechanism just set itself as the dataContext even though you tried binding the content Source to the button, you forgot to set the path, I guess this is why it got ignored.
all you have to do to make it work is just set it like that:
<ContentPresenter Content="{Binding Source={StaticResource menu}, Path=Content}"/
I am trying to figure the many different ways of setting datacontext of a view to a viewmodel.
One I'm oggling at this moment goes something like this:
I have my MainWindowResource:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:DemoStuffPartII.View"
xmlns:vm="clr-namespace:DemoStuffPartII.ViewModel">
<DataTemplate DataType="{x:Type vm:PersonViewModel}">
<vw:PersonView />
</DataTemplate>
But that's also immediately where I strand. I know that I should use a ContentControl in the View. But what is the best way to configure it? How to go about this?
That is the way you can enable ViewSwitching navigation in your MVVM application.
The other missing bits are:
in the view ->
<ContentControl Content="{Binding CurrentPage}" />
in the ViewModel -> (pseudo code)
Prop ViewModelBase CurrentPage.
note however that if all u want is to connect a ViewModel to a View, you can just drop the entire DataTemplate-ContentControl thing altogether, and just do this.DataContext = new SomeViewModel(); in the codebehind.
The cleanest way I know to connect VM to Views is by using the ViewModelLocator pattern. Google ViewModelLocator.
There are a couple of simple ways to just bind a ViewModel to a view. As Elad mentioned you can add it in the code-behind:
_vm = new MarketIndexVM();
this.DataContext = _vm;
or, you can specify the ViewModel as a resource in your XAML of your view:
<UserControl.Resources>
<local:CashFlowViewModel x:Key="ViewModel"/>
<Converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</UserControl.Resources>
and bind the DataContext of your LayoutRoot to that resource:
<Grid x:Name="LayoutRoot" DataContext="{StaticResource ViewModel}">
Maybe this doesn't directly answer your question, but have you looked at using an MVVM framework? For example, in Caliburn.Micro you would do (very basic example):
public class ShellViewModel : Conductor<IScreen>
{
public ShellViewModel()
{
var myViewModel = new MyViewModel();
this.ActivateItem(myViewModel);
}
}
ShellView.xaml
<Grid>
<ContentControl x:Name="ActiveItem" />
</Grid>
MyView.xaml
<Grid>
<TextBlock>Hello world</TextBlock>
</Grid>
This is a viewmodel first approach.
What simple thing am I missing here? Why doesn't my copy display on the screen?
<Window x:Class="DeleteThis.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>
<StackPanel>
<TextBlock Text="{Binding Path=SomeCopy}" Height="35" Width="100" Margin="10"/>
</StackPanel>
</Grid>
and my code-behind.
public partial class MainWindow : Window
{
private string _someCopy;
public string SomeCopy
{
get
{
return _someCopy;
}
set
{
_someCopy = value;
}
}
public MainWindow()
{
InitializeComponent();
SomeCopy = "why doesn't this display";
}
}
You never set the DataContext of the Window. Change your XAML to this...
<TextBlock Text="{Binding}" Height="35" Width="100" Margin="10"/>
...and change your code behind to add the DataContext line...
public MainWindow()
{
InitializeComponent();
SomeCopy = "why doesn't this display";
this.DataContext = SomeCopy;
}
Your current issue has nothing to do with needing a DependencyProperty as mentioned in the other answers.
WPF never finds out that the property changed.
To fix it, you can turn the property into a dependency property.
EDIT: You also need to bind to the property on the Window itself, like this
<TextBlock Text="{Binding Path=SomeCopy, RelativeSource={RelativeSource Self}}" ... />
SLaks's answer is the correct one. But making dependency properties manually is annoying, so I link you to my favorite solution: A custom PostSharp NotifyPropertyChangedAttribute that, when used in conjunction with PostSharp, makes all the properties of any given class into dependency properties.