I have a class (very little code for testing purposes) which contains a static ObservableCollection, which gets populated from elsewhere:
public class TestClass
{
public static ObservableCollection<int> TestCollection = new ObservableCollection<int>();
}
... and a basic WPF window with a ListBox:
<Window x:Class="app.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>
<ListBox x:Name="list"/>
</Grid>
</Window>
When I tried binding programmatically:
list.ItemsSource = Containers.TestClass.TestCollection;
... it worked just fine. However, when I try to perform binding through XAML:
<Window x:Class="app.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="clr-namespace:app.Containers"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<d:TestClass x:Key="dataSource"/>
</Window.Resources>
<Grid>
<ListBox x:Name="list" ItemsSource="{Binding Source={StaticResource dataSource}, Path=TestCollection}"/>
</Grid>
</Window>
... nothing gets displayed.
I've also tried setting DataContext:
<Window.Resources>
<l:LifeEngine x:Key="dataSource"/>
</Window.Resources>
<Window.DataContext>
<Binding Source="{StaticResource dataSource}"/>
</Window.DataContext>
...and using path...
...and nothing gets displayed once again.
Also, not sure if it matters, but when I make my class static I get an error in my XAML code saying:
The type 'TestClass' is abstract and must include an explicit value.
So, that's out of the question.
Any idea how I can bind that ObservableCollection through XAML?
Problem is you are trying to find static property on an instance object.
<ListBox ItemsSource="{Binding Source={StaticResource dataSource},
Path=TestCollection}"/>
Here dataSource is pointing to an instance of TestClass and by binding Path to TestCollection, you are asking to bind to an instance property TestCollection which is static instead. That's why it's not working.
You have to use x:Static markup extension to bind to static properties. (Note you are not creating any instance object of TestClass)
<ListBox ItemsSource="{Binding Source={x:Static d:TestClass.TestCollection}}"/>
Also be informed that static properties binds one time. If you change the instance at runtime that won't be reflected back on UI. In your case you are dealing with ObservableCollection so when you add/delete item from collection it will be refreshed on UI but in case you reinitialize the list that change won't be reflected back on UI. In case you want to update UI, you have to raise StaticPropertyChangedEvent. In case interested, check out my answer here.
Related
I have the following very simple WPF application:
A User control:
XAML:
<UserControl x:Class="WPFUserControlTest.TestControl"
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:WPFUserControlTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="root">
<Grid>
<TextBlock Text="{Binding ElementName=root, Path=InputString}"/>
</Grid>
Code Behind:
namespace WPFUserControlTest
{
public partial class TestControl : UserControl
{
public TestControl()
{
InitializeComponent();
}
public string InputString
{
get { return (string)GetValue(InputStringProperty); }
set { SetValue(InputStringProperty, value); }
}
public static readonly DependencyProperty InputStringProperty =
DependencyProperty.Register("InputString", typeof(string), typeof(TestControl), new PropertyMetadata(""));
}
}
My Main Window:
<Window x:Class="WPFUserControlTest.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:WPFUserControlTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<local:TestControl x:Name="Mercedes" InputString="Mercedes"/>
<local:TestControl InputString="Volvo"/>
</StackPanel>
</Grid>
</Window>
What i wonder is how it can be that the binding inside the user control that uses ElementName seems to work even though the main window changes the name of the control.
Is this binding done internally in the control somehow at compile time?
When i look at this is in the Live Visual Tree I see that one of my control instances has name "root" and one is named "mercedes". Still both of them works as expected...
You should read up on XAML namescopes. The name "Mercedes" is only applicable in the namescope of the window and "root" in the namescope of the UserControl.
The window cannot refer to the UserControl as "root" because it doesn't belong to the same namescope.
I have a ViewModel that has a property which is a ReadOnlyObservableCollection. Defined something like this:
public class MyViewModel
{
private ObservableCollection<string> myProtectedCollection;
public ReadOnlyObservableCollection<string> MyCollectionProperty { get; }
public MyViewModel()
{
this.myProtectedCollection = new ObservableCollection<string>();
this.MyCollectionProperty = new ReadOnlyObservableCollection<string>(this.myProtectedCollection);
this.myProtectedCollection.Add("String1");
this.myProtectedCollection.Add("String2");
this.myProtectedCollection.Add("String3");
}
}
I have then created a xaml file called TestData.xaml and set the build action to DesignData. In that I have this:
<local:MyViewModel
xmlns:local="clr-namespace:ScrapWpfApplication1"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<local:MyViewModel.MyCollectionProperty>
<system:String>String 1</system:String>
<system:String>String 2</system:String>
</local:MyViewModel.MyCollectionProperty>
</local:MyViewModel>
Finally I have a MainWindow.xaml with the following:
<Window x:Class="ScrapWpfApplication1.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:ScrapWpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
d:DataContext="{d:DesignData Source=SampleData.xaml}">
<ListBox ItemsSource="{Binding MyCollectionProperty}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
The problem is that this is not showing my sample data in the Visual Studio designer. If I change the collection in my view model to be a ObservableCollection instead of a ReadOnlyObservableCollection then it works as expcted.
I guess that this is because the design time data system is creating a dummy ReadOnlyCollection but XAML is unable to populate it because it is readonly.
Is there any way to get the design type data system to work without making my view model's collection property writeable?
Is there any way to get the design type data system to work without making my view model's collection property writeable?
Yes, you could create another view model class, to be used for design purposes only, with an ObservableCollection<T> property, and set the design time DataContext of the view to an instance of this one:
d:DataContext="{d:DesignInstance Type=local:DesignTimeViewModel, IsDesignTimeCreatable=True}
I've not seen a perfect answer to this. But this is what I have finally done.
Instead of trying to get the design data system to mock the readonly collection. I've created a new set of sample data just for the collection and made the MainWindow.xaml look at that instead.
So my TestData.xaml file changes to just this. In reality it has more in it but this is just a sample for this question so it looks fairly empty.
<local:MyViewModel
xmlns:local="clr-namespace:ScrapWpfApplication1"
xmlns:system="clr-namespace:System;assembly=mscorlib">
</local:MyViewModel>
Secondly I created a second test data file called TestDataArray.xaml with an array in it. Being sure to set the build action to DesignData.
<x:Array Type="system:String"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ScrapWpfApplication1"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String>String 1</system:String>
<system:String>String 2</system:String>
</x:Array>
Finally I changed my MainWindow.xaml file to this. Note the change to the binding on
<Window x:Class="ScrapWpfApplication1.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:ScrapWpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
d:DataContext="{d:DesignData Source=SampleData.xaml}">
<ListBox ItemsSource="{Binding}" DataContext="{Binding MyCollectionProperty}" d:DataContext="{d:DesignData Source=SampleDataArray.xaml}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
This works for my particular scenario, but it would fall down if the sample data was being bound to a control and the ReadOnlyCollection was being read by something inside that control.
I have used a StaticResource in XAML file. I have named the StaticResource using x:Name. Now I want to access the actual object of the StaticResource.
Here are the files:
In XAML file:
<Window x:Class="MyProject.MainWindow"
...
Title="MainWindow" Height="350" Width="525">
<Grid>
<StaticResource ResourceKey="MyButtonResource" x:Name="MyResource" />
</Grid>
</Window>
In Code-behind CS file:
Button buttonFromStaticResource = MyResource.SomeProperty as Button;
Here, I need something like SomeProperty or any method to get the actual object (in this case, it is a Button object).
Edit:
A way to get the object is to use TryFindResource:
Button buttonFromStaticResource = this.TryFindResource("MyButtonResource") as Button;
But this solution involves a string parameter. Any better solution than this so that I can use MyResource directly (by leveraging x:Name in XAML file), without using any string?
How to read static Resource in c#, Check a below code
<Window x:Class="MyProject.MainWindow"
...
Title="MainWindow" Height="350" Width="525">
<Grid>
<StaticResource ResourceKey="MyButtonResource" x:Name="MyResource" />
</Grid>
</Window>
c#
var currentResources= this.Resources["MyButtonResource"];
I'm just starting out with WPF having used WinForms for some time and seem to have fallen at the first hurdle.
I have my main XAMLdefined as
<Window x:Class="FHIRCDALoader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FHIRCDALoader.xaml"
Title="FHIR CDA Loader" Height="350" Width="525"
Icon="Icons/color_swatch.png">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New"
Executed="NewDocument" />
</Window.CommandBindings>
<DockPanel>
<local:menubar DockPanel.Dock="Top"/>
<local:toolbar DockPanel.Dock="Top"/>
<local:statusbar DockPanel.Dock="Bottom" />
<RichTextBox x:Name="Body"/>
</DockPanel>
</Window>
Note the use of the user controls, one of which is the "statusbar"
<UserControl x:Class="FHIRCDALoader.xaml.statusbar"
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="300" d:DesignWidth="300">
<StatusBar >
<StatusBarItem>
<TextBlock x:Name="bbstatusbar" />
</StatusBarItem>
</StatusBar>
</UserControl>
So in MainWindow.xaml.cs I see I can reference RichTextBox named body from the main XAML file. I can't however reference the TextBlock in the UserControl which is named "bbstatusbar".
How do I set the value of the TextBlock from MainWindow.xaml.cs?
In agreement with Vlad and HighCore's comments: you don't set the TextBlock from MainWindow.xaml.cs. You bind it to a view-model. A binding simply looks like this:
<TextBlock Text="{Binding StatusText}" />
The above says: bind the Text property to a property in the current data-context called "StatusText". Next, create a view model:
public class ViewModel : INotifyPropertyChanged
{
public string StatusText
{
get { return _statusText; }
set
{
_statusText = value;
RaisePropertyChanged("StatusText");
}
}
// TODO implement INotifyPropertyChanged
}
Finally, set the DataContext of your MainPage to the view model. You can do this in a variety of ways, but let's say here for simplicity, do it in the constructor:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel { StatusText = "hello world" };
}
Now, the idea is to put your model-related logic into ViewModel. So, you shouldn't need to access the UI elements -- instead, update the view-model properties that the UI elements are bound to.
I defined an object which contains properties in code-behind and, want to set the object to datacontext in xaml.
namespace WpfApplication4
{
public partial class MainWindow : Window
{
public EquipInfo equip1;
public MainWindow()
{
InitializeComponent();
equip1 = new EquipInfo();
// setting here works fine but i want to do in xaml
//textBox1.DataContext = equip1;
}
}
}
here's xaml code..
<Window x:Class="WpfApplication4.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>
<TextBox Text="{Binding Path=PIN}" Height="23"
HorizontalAlignment="Left" Margin="172,208,0,0"
Name="textBox1" VerticalAlignment="Top" Width="120" />
</Grid>
</Window>
You can set the DataContext via XAML to a public property. blindmeis has given a good example on how to do this.
What you did in your Post was not setting the DataContext, but binding the Text Property of your Control to a Property of the window. This only works if a DataContext is set. Another way to do that, if you don't want to do MVVM (which I'd recommend, though), is referencing specific Elements in your Binding:
1.) Alter member to AutoProperty or similar
public EquipInfo equip1 {get;set;}
2.) Give your window a name
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="myWin"
Title="MainWindow" Height="350" Width="525">
2.) Access Window Properties by instance name
<TextBox Text="{Binding ElementName=myWin, Path=equip1.PIN}"/>
On a side note, it would really be better to use a MVVM instead.
when you create your equipinfo class in codebehind why not setting the datacontext there?
nevertheless you can create a datacontext in xaml (you did not need the codebehind stuff then)
<TextBox Text="{Binding Path=PIN}">
<TextBox.DataContext>
<local:equip1/>
</TextBox.DataContext>
</TextBox>
but please look at the MVVM pattern
When you create your equipinfo class in codebehind why not setting the datacontext there?
The whole point of MVVM is to seperate UI from code behind using a viewmodel proxy. If you are going to reference the ui element to set its datacontext you might just as well reference it to set its value and forget about mvvm.