WPF ComboBox binding to static array (or collection) [duplicate] - c#

This question already has answers here:
DataBinding in WPF?
(1 answer)
Binding to static property
(12 answers)
Closed 1 year ago.
Everything I've looked at on ComboBox binding shows different implementations than what I'm attempting to do, and my attempts at configuring it myself have failed.
Here's a simplistic example of what I'm trying to accomplish.
Code file:
namespace TestApp
{
public record ItemInfo
{
public int ID;
public string name;
public string description;
public decimal value;
public bool available;
}
public static class Data
{
public static ItemInfo[] myItems = new ItemInfo[10];
}
}
XAML:
<Window
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:TestApp"
x:Class="TestApp.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.DataContext>
<Binding Source="local:Data"/>
</Grid.DataContext>
<ComboBox Grid.Row="0"
VerticalAlignment="Center" HorizontalAlignment="Center"
Width="400" Height="40"
ItemsSource="{Binding myItems[]}" DisplayMemberPath="name">
</ComboBox>
</Grid>
</Window>
On a side note, lest there be confusion, I've got a separate method (not shown) that populates the myItems array. I know this is working and contains the desired data, as when the program is running I can put a watch on it and see the proper values in the array.
What I'm attempting to do is to have the ComboBox contain all of the Data.myItems[].name values. The above code shows my most recent attempt to bind it, and while it builds successfully and doesn't throw any errors, the ComboBox is still empty.
There is an intellisense ... error under the myItems[] binding stating "No data context found for Binding myItems{}"
I've also tried moving the myItems[] to the Grid.DataContext -- <Binding Source="local:Data:myItems[]} and set ItemsSource to simply {Binding Path="name"} to no avail.
Also tried creating a static Collection of ItemInfo records, and set the Grid data context binding to that collection.. to no avail
What's the key to binding to static classes/objects?

What's the key to binding to static classes/objects?
1) Getting the value of a static member
In order not to confuse oneself, it is better to make the bindable member a read-only property:
public static class Data
{
public static ItemInfo[] MyItems {get;} = new ItemInfo[10];
}
<ComboBox Grid.Row="0"
VerticalAlignment="Center" HorizontalAlignment="Center"
Width="400" Height="40"
ItemsSource="{x:Static local:Data.MyItems}"
DisplayMemberPath="name">
You can get the Enum value in the same way.
2) Binding a static property.
Binding means "tracking" changes in the value of the source property.
To do this, you need to create a static event.
An example of a static wrapper for a clock and binding to it:
using System.Threading;
public static class ClockForWpf
{
private static readonly Timer timer = new Timer(Tick, null, 0, 10);
private static void Tick(object state)
{
Time = DateTime.Now;
TimeChanged?.Invoke(null, EventArgs.Empty);
}
public static event EventHandler TimeChanged;
public static DateTime Time { get; private set; }
}
<TextBlock Text="{Binding Path=(local:ClockForWpf.Time)}"/>

Related

Databinding ListBox SelectedItems to ViewModel

I'm attempting to databind the ListBox SelectedItems attribute using an attached property I've created. I set up a class called ListBoxFix which is located in a folder called ControlFixes. It's code is a very simple dependency property shown below:
using System.Windows;
using System.Windows.Controls;
namespace QMAC.ControlFixes
{
public static class ListBoxFix
{
public static bool GetSelectedItemsBinding(ListBox element)
{
return (bool)element.GetValue(SelectedItemsBindingProperty);
}
public static void SetSelectedItemsBinding(ListBox element, bool value)
{
element.SetValue(SelectedItemsBindingProperty, value);
if (value)
{
element.SelectionChanged += (sender, args) =>
{
var x = element.SelectedItems;
};
}
}
public static readonly DependencyProperty SelectedItemsBindingProperty =
DependencyProperty.RegisterAttached("FixSelectedItemsBinding",
typeof(bool), typeof(FrameworkElement), new PropertyMetadata(false));
}
}
In my XAML code I have the following markup:
<Window x:Class="QMAC.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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
xmlns:fix="clr-namespace:QMAC.ControlFixes"
x:Name="Window"
DataContext="{Binding Main, Mode=OneWay, Source={StaticResource Locator}}"
Title="QMAC" Width="554.779" ResizeMode="CanMinimize" Height="539" Icon="logo.ico" >
<Grid Background="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" RenderTransformOrigin="0.593,0.948" Margin="0,0,0,1">
<ListBox x:Name="schoolListBox" HorizontalAlignment="Left" Margin="25,86,0,0" Width="274" FontSize="16" SelectionMode="Extended" ItemsSource="{Binding LocationList}" fix:ListBox.SelectedItemsBindingProperty="true" VerticalAlignment="Top" Height="364"></ListBox>
</Grid>
</Window>
Unfortunately, I'm getting the 3 errors to how I've setup my markup. They are
Error 1 The name "ListBox" does not exist in the namespace "clr-namespace:QMAC.ControlFixes".
Error 2 The attachable property 'SelectedItemsBindingProperty' was not found in type 'ListBox'.
Error 3 The property 'ListBox.SelectedItemsBindingProperty' does not exist in XML namespace 'clr-namespace:QMAC.ControlFixes'.
I'm mainly trying to understand why it's looking for ListBox in my ControlFixes namespace?
You declare and use the attached property in a wrong way. I would suggest you to read carefully this well written overview.
There are following mistakes in your code:
The owner type for your attached property is incorrectly specified as FrameworkElement.
The registered property name does not match the static field containing it
You try to use your attached property via the ListBox class although you have defined it in your ListBoxFix class.
The proper attached property definition should look similar to this:
public static class ListBoxFix
{
public static bool GetSelectedItemsBinding(ListBox element)
{
return (bool)element.GetValue(SelectedItemsBindingProperty);
}
public static void SetSelectedItemsBinding(ListBox element, bool value)
{
element.SetValue(SelectedItemsBindingProperty, value);
}
public static readonly DependencyProperty SelectedItemsBindingProperty =
DependencyProperty.RegisterAttached("SelectedItemsBinding",
typeof(bool), typeof(ListBoxFix), new PropertyMetadata(false));
}
Note that the ownerType parameter of the RegisterAttached() method provides the type of your class containing the attached property. Take a look on the name parameter too.
The proper usage of your attached property:
<ListBox fix:ListBoxFix.SelectedItemsBinding="true"/>
Update:
You might want to use your attached property in a "WPF" style. Then it would be better to design your class to be derived from DependencyObject. This is what MSDN states:
If your class is defining the attached property strictly for use on other types, then the class does not have to derive from DependencyObject. But you do need to derive from DependencyObject if you follow the overall WPF model of having your attached property also be a dependency property.

WPF ComboBox binding ItemsSource

I'm a beginner on WPF and trying to bind the Items of a ComboBox to an ObservableCollection
I used this code:
XAML
<Window x:Class="comboBinding2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox x:Name="cmbTest" ItemsSource="{Binding Path=cmbContent}" Width="200" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</Window>
C#
public MainWindow()
{
cmbTest.ItemsSource = cmbContent;
cmbContent.Add("test 1");
cmbContent.Add("test 2");
InitializeComponent();
}
public ObservableCollection<string> cmbContent { get; set; }
I don't get any errors on this Code until I try to debug, it throws the error:
TargetInvocationError
An unhandled exception of type 'System.Reflection.TargetInvocationException' occurred in PresentationFramework.dll
Can anybody tell me what I'm doing wrong?
There are a few things wrong with your current implementation. As others have stated, your list is currently NULL, and the DataContext of the Window is not set.
Though, I would recommend (especially since you just started using WPF) is learning to do the binding the more 'correct' way, using MVVM.
See the simplified example below:
First, you want to set the DataContext of your Window. This will allow the XAML to 'see' the properties within your ViewModel.
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
Next, simply set up a ViewModel class that will contain all of the Window's binding elements, such as:
public class ViewModel
{
public ObservableCollection<string> CmbContent { get; private set; }
public ViewModel()
{
CmbContent = new ObservableCollection<string>
{
"test 1",
"test 2"
};
}
}
Lastly, update your XAML so that the binding path matches the collection:
<Grid>
<ComboBox Width="200"
VerticalAlignment="Center"
HorizontalAlignment="Center"
x:Name="cmbTest"
ItemsSource="{Binding CmbContent}" />
</Grid>
public MainWindow()
{
InitializeComponent();
cmbContent=new ObservableCollection<string>();
cmbContent.Add("test 1");
cmbContent.Add("test 2");
cmbTest.ItemsSource = cmbContent;
}
public ObservableCollection<string> cmbContent { get; set; }
The code above don't use any binding, that's mean using it there no need to bind the Combobox's ItemSource, if you wan't to use binding you need to
First: Set the DataContext from the CodeBehind (ViewModel) using :
this.DataContext=this;
or from the Xaml:
DataContext="{Binding RelativeSource={RelativeSource Self}}">
Second : use the Binding in the ItemSource Just like you did ItemsSource="{Binding Path=cmbContent}" you may also considere using INotifyPropertyChanged Interface if you want to Notify the UI in case of any changes in a property
cmbContent is null because you never set it to anything. I'm guessing the error is actually a NullReferenceException, but it is showing up as TargetInvocationException because it is in the constructor of a view.
Also, you're setting the ItemsSource of the ComboBox twice (once in the binding, once in the constructor). You don't need to do that. Pick one. Your binding won't work the way it is written (because the DataContext isn't set) so you should either go with doing it in code, or set up the DataContext (as suggested by Nadia).

How to access a static variable from code behind? [duplicate]

This question already has answers here:
Setting Label Text in XAML to string constant
(3 answers)
Closed 9 years ago.
Question: Is it possible to access a static variable from code behind to be used in XAML?
Reason: I want a single string variable to keep a menu name which will be used in different places (in code behind and also in XAML).
Example (code behind):
public partial class MainWindow : Window
{
public static readonly string menuName = "MyMenu";
... other code ...
}
Example (XAML):
<MenuItem Header="... here I want my menuName to appear ..." />
for that you would need to instantiate your class in xaml once, then you can use the static member.
it would be better to create a separate class for static variables and load it in xaml in resources.
something like this
<Window.Resources>
<!-- Create an instance of the class called MyClass -->
<my:MyClass x:Key="MyClass" />
</Window.Resources>
then use it as something like
<TextBox Text="{x:Static my:MyClass.MyProperty}" Width="500" Height="100" />
or
<TextBlock Text="{Binding Source={StaticResource MyClass},Path=MyProperty}" />
also see
XAML Binding to static classes
How to bind in XAML to a static property?
You should add it to project Resource dictionary:
go to you project -> Properties -> Resources-> Add Resource Button
then you can use it in Xaml or code behind like that:
-- XAML---
<MenuItem Header="{x:Static properties:Resources.menuName}" />
--- Code behind ----
Properties.Resources.menuName
you can not do this for the simple reason that when you bind on your property you will get an infinite nested calls to MainWindow which will generate an 'System.StackOverflowException' you should use a container class like this
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
}
public class WindowMessagesManager
{
private static string _header;
public static string Header1
{
get { return "My Header"; }
set { _header = value; }
}
}
}
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prop="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<prop:WindowMessagesManager x:Key="window" ></prop:WindowMessagesManager>
//you can try to uncomment this and you will get an exception
<!--<prop:MainWindow x:Key="window"></prop:MainWindow>-->
</Window.Resources>
<Grid>
<Menu>
<MenuItem Height="100" Width="100" Header="{Binding Source={StaticResource ResourceKey=window}, Path=Header1}"></MenuItem>
</Menu>
</Grid>
</Window>

How can you bind an attached property to DisplayMemberPath of a ComboBox?

I can't seem to find the correct syntax to allow an attached property to be used as the DisplayMemberPath of a ComboBox.
The property is SelectorSwitchedControl.NameForSelector
It's in the namespace 'LocalTest' which is mapped to the XAML prefix 'local'.
Here's the code...
<UserControl x:Class="Playground.SelectorSwitchedControlTest.SelectorSwitchedControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:glc="clr-namespace:Playground.CommonControls"
xmlns:local="clr-namespace:Playground.SelectorSwitchedControlTest"
Background="Transparent">
<Border x:Name="MainBorder"
BorderBrush="Gray" BorderThickness="1">
<DockPanel>
<glc:FixedToolBar DockPanel.Dock="Top">
<ComboBox x:Name="MainSelector"
ItemsSource="{Binding Children, ElementName=MainPanel}"
DisplayMemberPath="(local:SelectorSwitchedControl.NameForSelector)" />
</glc:FixedToolBar>
<local:SelectorSwitchedControlPanel x:Name="MainPanel" />
</DockPanel>
</Border>
</UserControl>
...which for some reason gives me the exception 'Prefix 'local' does not map to a namespace.' which I'm not sure why it's saying that as if I remove the 'DisplayMemberPath' line, the '' tag renders just like it's supposed to proving the namespace is mapped.
I've also tried all of the following...
DisplayMemberPath="local:SelectorSwitchedControl.NameForSelector"
DisplayMemberPath="(local:SelectorSwitchedControl.NameForSelector)"
DisplayMemberPath="SelectorSwitchedControl.NameForSelector"
DisplayMemberPath="(SelectorSwitchedControl.NameForSelector)"
DisplayMemberPath="LocalTest.SelectorSwitchedControl.NameForSelector"
DisplayMemberPath="(LocalTest.SelectorSwitchedControl.NameForSelector)"
I know it's just one of those days where my mind isn't working and I'm missing something simple, but it's driving me crazy! So what's the proper syntax?
DisplayMemberPath - path to the display string property for each item. Set it to "NameForSelector", not to "{Binding NameForSelector}".
<DockPanel>
<ComboBox x:Name="MainSelector" ItemsSource="{Binding Children}" DisplayMemberPath="NameForSelector" />
</DockPanel>
public class SelectorSwitchedControl
{
public string Name { get; set; }
public string NameForSelector{ get; set; }
}
I think it's just not possible to use an attached property in DisplayMemberPath with regular controls. The reason is that the property path you are using refers to an XML namespace declared in your XAML. Normally when you use the attached property syntax there is a parser context available when the XAML/BAML reader is creating the objects and this context supplies the namespace information. However DisplayMemberPath is just a string and does not capture this context, so this context is not available to supply the namespace information at the point where your property path is actually used to create a binding. From my reading of code in PresentationFramework.dll, you might be able to supply the context through the target object (the one to which your property is attached) by having it implement IServiceProvider and return a suitable IXamlTypeResolver (relevant code starts from PropertyPath.GetTypeFromName).
As a cheaper alternative, consider a template or template selector instead of DisplayMemberPath. If you want to use the default lookup mechanisms, try something along the lines of
<ItemTemplate>
<DataTemplate>
<ContextPresenter
Content="{Binding (local:SelectorSwitchedControl.NameForSelector)}"/>
</DataTemplate>
</ItemTemplate>
The correct value is
DisplayMemberPath="(local:SelectorSwitchedControl.NameForSelector)"
If that is not working then I would use Snoop (http://snoopwpf.codeplex.com/) to make sure that the value is getting set correctly.
Here is the simplest working example
Xaml:
<Window x:Class="WPFTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFTest"
Title="MainWindow" Height="350" Width="525" Loaded="MainWindow_Loaded">
<Grid>
<ComboBox Name="cb" DisplayMemberPath="(local:MainWindow.TestValue)" />
</Grid>
Code:
public static string GetTestValue(DependencyObject element)
{
return (string)element.GetValue(TestValueProperty);
}
public static void SetTestValue(DependencyObject element, string value)
{
element.SetValue(TestValueProperty, value);
}
public static readonly DependencyProperty TestValueProperty = DependencyProperty.RegisterAttached("TestValue", typeof(string), typeof(MainWindow), new FrameworkPropertyMetadata(null));
private void MainWindow_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
TextBlock tb = default(TextBlock);
for (int i = 10; i <= 15; i++)
{
tb = new TextBlock();
tb.Text = "Text for " + i;
tb.SetValue(TestValueProperty, "Property For " + i);
this.cb.Items.Add(tb);
}
}

Access codebehind variable in XAML

How can I access the public variable which in Sample.xaml.cs file like asp.net <%=VariableName%>?
There are a few ways to do this.
Add your variable as a resource from codebehind:
myWindow.Resources.Add("myResourceKey", myVariable);
Then you can access it from XAML:
<TextBlock Text="{StaticResource myResourceKey}"/>
If you have to add it after the XAML gets parsed, you can use a DynamicResource above instead of StaticResource.
Make the variable a property of something in your XAML. Usually this works through the DataContext:
myWindow.DataContext = myVariable;
or
myWindow.MyProperty = myVariable;
After this, anything in your XAML can access it through a Binding:
<TextBlock Text="{Binding Path=PropertyOfMyVariable}"/>
or
<TextBlock Text="{Binding ElementName=myWindow, Path=MyProperty}"/>
For binding, if DataContext is not in use, you can simply add this to the constructor of the code behind:
this.DataContext = this;
Using this, every property in the code becomes accessible to binding:
<TextBlock Text="{Binding PropertyName}"/>
Another way is to just give a name to the root element of the XAML:
x:Name="root"
Since the XAML is compiled as a partial class of the code-behind, we can access every property by name:
<TextBlock Text="{Binding ElementName="root" Path=PropertyName}"/>
Note: access is only available to properties; not to fields. set; and get; or {Binding Mode = OneWay} are necessary. If OneWay binding is used, the underlying data should implement INotifyPropertyChanged.
For quick-and-dirty Windows in WPF, I prefer binding the DataContext of the Window to the window itself; this can all be done in XAML.
Window1.xaml
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource self}}"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TextBlock Text="{Binding Path=MyProperty1}" />
<TextBlock Text="{Binding Path=MyProperty2}" />
<Button Content="Set Property Values" Click="Button_Click" />
</StackPanel>
</Window>
Window1.xaml.cs
public partial class Window1 : Window
{
public static readonly DependencyProperty MyProperty2Property =
DependencyProperty.Register("MyProperty2", typeof(string), typeof(Window1), new UIPropertyMetadata(string.Empty));
public static readonly DependencyProperty MyProperty1Property =
DependencyProperty.Register("MyProperty1", typeof(string), typeof(Window1), new UIPropertyMetadata(string.Empty));
public Window1()
{
InitializeComponent();
}
public string MyProperty1
{
get { return (string)GetValue(MyProperty1Property); }
set { SetValue(MyProperty1Property, value); }
}
public string MyProperty2
{
get { return (string)GetValue(MyProperty2Property); }
set { SetValue(MyProperty2Property, value); }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Set MyProperty1 and 2
this.MyProperty1 = "Hello";
this.MyProperty2 = "World";
}
}
In the above example, note the binding used in the DataContext property on the Window, this says "Set your data context to yourself". The two text blocks are bound to MyProperty1 and MyProperty2, the event handler for the button will set these values, which will automatically propagate to the Text property of the two TextBlocks as the properties are Dependency Properties.
It is also worth noting that a 'Binding' can only be set on a DependencyProperty of a DependencyObject. If you want to set a non DependencyProperty (eg. a normal property) on an object in XAML, then you will have to use Robert's first method of using resources in the code behind.
myWindow.xaml
<Window
...
<TextBlock Text="{ Binding Path=testString }" />
</Window>
myWindow.xaml.cs
public partial class myWindow: Window
{
public string testString { get; set; } = "This is a test string";
public myWindow()
{
DataContext = this;
InitializeComponent();
}
}
Important
Set Datacontext
testString MUST be public
testString MUST be a property (have a get and set)

Categories

Resources