Why my binding don't work on a ObservableCollection - wpf

Hello I am trying to bind a ItemsSource to a ObservableCollection. It seems the ObservableCollection is not seen by the IntelliSense event if the ObservableCollection is public.
Do I have the declare something in XAML to make it visible ? Like in Window.Ressources.
My XAML code
<Window x:Class="ItemsContainer.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 Orientation="Horizontal">
<ListBox ItemsSource="{Binding StringList}" />
</StackPanel> </Window>
My C# code
using System.Collections.ObjectModel;
using System.Windows;
namespace ItemsContainer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<string> stringList = new ObservableCollection<string>();
public ObservableCollection<string> StringList
{
get
{
return this.stringList;
}
set
{
this.stringList = value;
}
}
public MainWindow()
{
InitializeComponent();
this.stringList.Add("One");
this.stringList.Add("Two");
this.stringList.Add("Three");
this.stringList.Add("Four");
this.stringList.Add("Five");
this.stringList.Add("Six");
}
}
}
To the best of my knowledge that binding should bind to the property StringList of the current
DataContext, which is MainWindow.
Thanks for any pointer.
Edit:
This worked for me in XAML
<Window x:Class="ItemsContainer.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 Orientation="Horizontal">
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=StringList}" />
</StackPanel>
</Window>

The DataContext does not default to the MainWindow, you'd have to explicitly set that. Like so:
public MainWindow() {
InitializeComponent();
this.stringList.Add("One");
this.stringList.Add("Two");
this.stringList.Add("Three");
this.stringList.Add("Four");
this.stringList.Add("Five");
this.stringList.Add("Six");
this.DataContext = this;
}

Related

WPF - MVVM binding in UserControl

I'm testing a sample binding in MVVM pattern. I'm using package GalaSoft.MvvmLight. Binding from MainViewModel to MainWindow is normal but I can't binding data from a ViewModel (ImageViewModel) to View (ImageView). All my code is below
in App.xaml
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-
namespace:WpfApplication1" StartupUri="MainWindow.xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d"
xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-
namespace:WpfApplication1.ViewModel" />
</ResourceDictionary>
</Application.Resources>
</Application>
in MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:WpfApplication1.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
DataContext="{Binding Main, Source={StaticResource Locator}}"
mc:Ignorable="d"
Title="MainWindow" Height="800" Width="1000">
<Grid>
<Grid Grid.Column="1">
<v:ImageView DataContext="{Binding ImageVM}"/>
</Grid>
</Grid>
in ImageView.xaml
<UserControl x:Class="WpfApplication1.View.ImageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1.View"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Name="ucImage">
<UserControl.DataContext>
<Binding Path="Main.ImageVM" Source="{StaticResource Locator}"/>
</UserControl.DataContext>
<Grid>
<TextBox x:Name="label" Text="{Binding TestText, ElementName=ucImage}" Width="100"
Height="50"/>
</Grid>
</UserControl>
in ViewModelLocator.cs
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApplication1.ViewModel
{
public class ImageViewModel: ViewModelBase
{
public string _TestText;
public string TestText
{
get
{
return _TestText;
}
set
{
_TestText = value;
RaisePropertyChanged(() => this.TestText);
}
}
public ImageViewModel()
{
TestText = "asdasdasdasdas";
}
}
}
in MainViewModel
public class MainViewModel : ViewModelBase
{
private ImageViewModel _ImageVM;
public ImageViewModel ImageVM
{
get { return _ImageVM; }
set { Set(ref _ImageVM, value); }
}
public MainViewModel()
{
ImageVM = new ImageViewModel();
}
}
in ImageViewModel.cs
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApplication1.ViewModel
{
public class ImageViewModel: ViewModelBase
{
public string _TestText;
public string TestText
{
get
{
return _TestText;
}
set
{
_TestText = value;
RaisePropertyChanged(() => this.TestText);
}
}
public ImageViewModel()
{
TestText = "asdasdasdasdas";
}
}
}
This error from output is
System.Windows.Data Error: 40 : BindingExpression path error:
'TestText' property not found on 'object' ''ImageView'
(Name='ucImage')'. BindingExpression:Path=TestText;
DataItem='ImageView' (Name='ucImage'); target element is 'TextBox'
(Name='label'); target property is 'Text' (type 'String')
Anyone who comes up with a solution would be greatly appreciated!
The expression
Text="{Binding TestText, ElementName=ucImage}"
expects a TestText property in the ImageView control, which apperently does not exists - that is what the error message says.
You would simply write the following to make the element in the control's XAML bind directly to the view model object in its DataContext:
<TextBox Text="{Binding TestText}" .../>
In order to make the control independent of a specific view model, it should expose a bindable property, i.e. a dependency property like this:
public partial class ImageView : UserControl
{
public ImageView()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
nameof(Text), typeof(string), typeof(ImageView));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
which would work with
<TextBox Text="{Binding Text, ElementName=ucImage}"
in the control's XAML and without explictly setting the control's DataContext, i.e. without the <UserControl.DataContext> section in its XAML.
The property would be bound like
<v:ImageView DataContext="{Binding ImageVM}" Text="{Binding TestText}"/>
or just
<v:ImageView Text="{Binding ImageVM.TestText}"/>

WPF Radio Buttons

I am creating Radio button on Visual Studio and I am having a trouble with Binding
This is my button:
<RadioButton VericalAlignment="Center" GroupName="group1" Content="name"></RadioButton>
I want that if the button was selected then the value of {"someString " + Content Value} will be bind
to SomeClass.variable
is something like this possible?
Thank you!
Something like this should work:
XAML:
<Window x:Class="StackOverflowRadioButton.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StackOverflowRadioButton"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Margin="81,36,-81,-36">
<RadioButton Content="RadioButton" HorizontalAlignment="Left" Margin="111,135,0,0" VerticalAlignment="Top"
Name="myRadioButton" Checked="myRadioButton_Checked"/>
</Grid>
CS file:
using System.Windows;
namespace StackOverflowRadioButton
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public string someVariable;
public MainWindow()
{
InitializeComponent();
}
private void myRadioButton_Checked(object sender, RoutedEventArgs e)
{
someVariable = "someString " + myRadioButton.IsChecked;
}
}
}

Closing-EventTrigger on a WPF Window - Problems with DataContext

I have a view, which initializes a viewmodel inside the windows resources. Further more I give my grid the DataContext.
My question is, how I can add a command to my windows closing event keeping mvvm in memory? I tried the version of this post:
Handling the window closing event with WPF / MVVM Light Toolkit
... but its not working using an event-trigger, because I can't access the viewmodel from outside my grid, so I can't access my command.
Any solution for my problem?
Greetings
Jannik
Edit: Here's my xaml:
<Window x:Class="WpfApplication1.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:WpfApplication1.ViewModels"
xmlns:converter="clr-namespace:WpfApplication1.Converter"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewModels:MainWindowViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">...</Grid>
</Window>
You can reference to members of a static resource this way:
Command="{Binding Path=CloseCommand, Source={StaticResource ViewModel}}"
Here's the complete test project. I used a text box with a binding to ensure data is saved.
<Window x:Class="WpfApplication1.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:WpfApplication1.ViewModels"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewModels:MainWindowViewModel x:Key="ViewModel"/>
</Window.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding Path=CloseCommand, Source={StaticResource ViewModel}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid DataContext="{StaticResource ViewModel}">
<TextBox Text="{Binding Txt, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
In ViewModel code, I used a static reference to store data (LastInstance). you can replace it with your own method.
Also I used Command which is a custom implementation of ICommand. If you want I can add the complete implementation here.
public MainWindowViewModel()
{
//save or load here...
if (LastInstance != null) Txt = LastInstance.Txt;
CloseCommand = new Command(o => LastInstance = this);
//...
}
public static ViewModel LastInstance;
//Txt Dependency Property
public string Txt
{
get { return (string)GetValue(TxtProperty); }
set { SetValue(TxtProperty, value); }
}
public static readonly DependencyProperty TxtProperty =
DependencyProperty.Register("Txt", typeof(string), typeof(ViewModel), new UIPropertyMetadata(null));
//CloseCommand Dependency Property
public Command CloseCommand
{
get { return (Command)GetValue(CloseCommandProperty); }
set { SetValue(CloseCommandProperty, value); }
}
public static readonly DependencyProperty CloseCommandProperty =
DependencyProperty.Register("CloseCommand", typeof(Command), typeof(ViewModel), new UIPropertyMetadata(null));
The typical approach to this problem is to have a MainViewModel and set the DataContext of you Window to it. Then define other viewModels in the MainViewModel.
<Window>
<Grid DataContext="{Binding MyGridViewModel}">
</Grid>
<DockPanel DataContext="{Binding AnotherViewModel}">
</DockPanel>
</Window>
in MainWindow constructor:
this.DataContext = new MainViewModel();
in MainViewModel constructor:
this.MyGridViewModel = new OtherViewModel();
This way you have many options to find the desired object through viewModel references.

binding to a usercontrol not working

i have encountered a databinding probleme,
so i create a usercontrole
UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public static DependencyProperty TestThisProperty = DependencyProperty.Register
(
"TestThis",
typeof(string),
typeof(UserControl1),
new PropertyMetadata("Some Data",new PropertyChangedCallback(textChangedCallBack))
);
public string TestThis
{
get { return (string)GetValue(TestThisProperty); }
set { SetValue(TestThisProperty, value); }
}
static void textChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
UserControl1 _us = (UserControl1)property;
_us.MyUserControl.TestThis = (string)args.NewValue;
}
}
UserControl1.xaml
<UserControl x:Class="WpfApplication1.UserControl1"
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" x:Name="MyUserControl"
d:DesignHeight="300" d:DesignWidth="300">
</UserControl>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
UserControl1 _uc = new UserControl1();
public MainWindow()
{
InitializeComponent();
DispatcherTimer _dt = new DispatcherTimer();
_dt.Interval = new TimeSpan(0, 0, 1);
_dt.Start();
_dt.Tick += new EventHandler(_dt_Tick);
}
private void _dt_Tick(object s,EventArgs e)
{
_uc.TestThis = DateTime.Now.ToString("hh:mm:ss");
}
and finaly the MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Text="{Binding Path=TestThis,ElementName=MyUserControl, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
but the problem here, whene i debug my code i get this warning
Cannot find source for binding with reference 'ElementName=MyUserControl'. and the ui does not update of course,any idea please?
ElementName only targets names in the same namescope, e.g.
<TextBox Name="tb"/>
<Button Content="{Binding Text, ElementName=tb}"/>
If you do not define the UserControl in the MainWindow XAML and give it a name there or register a name in code behind (and target that) you won't get this ElementName binding to work.
An alternative would be exposing the usercontrol as a property on the MainWindow, e.g.
public UserControl1 UC { get { return _uc; } }
Then you can bind like this:
{Binding UC.TestThis, RelativeSource={RelativeSource AncestorType=Window}}

WPF - CollectionViewSource Filter event in a DataTemplate not working

I'm seeing some really weird behavior where WPF isn't doing what I expect it to do. I've managed to boil the problem down the following bit of code:
XAML:
<Window x:Class="WpfApplication3.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">
<TabControl x:Name="tabControl">
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type List}">
<UserControl>
<UserControl.Resources>
<CollectionViewSource x:Key="filteredValues" Source="{Binding}" Filter="CollectionViewSource_Filter" />
</UserControl.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource filteredValues}}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
Code-behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication3
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.tabControl.ItemsSource = new List<List<string>>()
{
new List<string>() { "a", "b", "c"},
};
}
private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
{
string item = (string)e.Item;
e.Accepted = item.StartsWith("b");
}
}
}
I'd expect that this code would result in a TabControl with a single tab that has a ListBox with a single item that says "b." But, instead, I get a ListBox with all 3 of the strings. Setting a breakpoint inside CollectionViewSource_Filter shows that the filter never even runs.
What's going on here? Why isn't the filter working?
I was thinking maybe it has something to do with the CollectionViewSource being a resource in a DataTemplate. The events on the ListBox fire properly. If the UserControl is not part of a DataTemplate, the Filter event works fine.
EDIT:
For example, the following works as expected, with the List being filtered as expected.
XAML:
<Window x:Class="WpfApplication3.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">
<UserControl>
<UserControl.Resources>
<CollectionViewSource x:Key="filteredValues" Source="{Binding}" Filter="CollectionViewSource_Filter" />
</UserControl.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource filteredValues}}" />
</UserControl>
</Window>
Code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication3
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new List<string>() { "a", "b", "c" };
}
private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
{
string item = (string)e.Item;
e.Accepted = item.StartsWith("b");
}
}
}
Well, I don't know why it doesn't work, but at this point, I'm assuming it's a Microsoft bug. I'll probably be filing a Connect report shortly.
To work around the bug, I did the following. I created a subclass of CollectionViewSource like this:
using System.Windows.Data;
namespace WpfApplication3
{
internal class CustomFilteredCollectionViewSource : CollectionViewSource
{
public CustomFilteredCollectionViewSource()
: base()
{
this.Filter += CustomFilter;
}
private void CustomFilter(object sender, FilterEventArgs args)
{
string item = (string)args.Item;
args.Accepted = item.StartsWith("b");
}
}
}
I then replaced
<CollectionViewSource x:Key="filteredValues" Source="{Binding}" Filter="CollectionViewSource_Filter" />
with
<local:CustomFilteredCollectionViewSource x:Key="filteredValues" Source="{Binding}" />
and it now works perfectly.
You're using the Filter as if it's a property on the CollectionViewSource which always gets used.
It isn't. It's an event. It says, "When you filter this CollectionViewSource, this event will be called." It will respond to requests to filter, but won't trigger those requests itself.
I don't know a huge amount about CollectionViewSource, but I assume you'd have to bind it to a filtering control in order for this event to be triggered, like a Grid which allowed filtering.
I came across this same issue today and found a much easier workaround.
The issue is that the Filter event is not subscribed properly for some reason. You can get around this by subscribing to Filter in the Loaded event of the control which contains the CollectionViewSource as a resource.
Applying this to the example in the question you get
XAML:
<Window x:Class="WpfApplication3.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">
<TabControl x:Name="tabControl">
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type List}">
<UserControl Loaded="UserControl_OnLoaded">
<UserControl.Resources>
<CollectionViewSource x:Key="filteredValues" Source="{Binding}"/>
</UserControl.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource filteredValues}}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Code Behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication3
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.tabControl.ItemsSource = new List<List<string>>()
{
new List<string>() { "a", "b", "c"},
};
}
private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
{
string item = (string)e.Item;
e.Accepted = item.StartsWith("b");
}
private void UserControl_OnLoaded(object sender, RoutedEventArgs e)
{
var control = (UserControl) sender;
var cvs = (CollectionViewSource) control.Resources["filteredValues"];
cvs.Filter += CollectionViewSource_Filter;
}
}
}

Resources