Binding ListBox ItemsSource to method call results at compile time? - c#

I'm new to WPF data binding.
I have a ListBox on a form that I want to bind to the results of the following method call:
RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)
.OpenSubKey(#"SOFTWARE\Vendor\Product\Systems").GetSubKeyNames();
At the moment I'm doing it at runtime by assigning ListBox.ItemsSource = (method); in the Window_Loaded() event handler. But this means that the source data for the control is non-obvious when looking at the control configuration in the form editor.
Is there a way to configure this binding within the XAML so that it is visible in the form editor, to make the behavior of the code easier to understand?
Most of the examples in the MSDN documentation bind the controls to static resources, like in-line XAML resources. I've noticed that there is an ObjectDataProvider class which provides "[...] the ability to bind to the result of a method." However I am finding the examples in the ObjectDataProvider documentation quite confusing. I'd appreciate some advice on whether that's the right way to do this binding, and if so, what syntax to use when declaring the ObjectDataProvider.

In short, I don't think you can use such a complex statement directly in your XAML. As you've found, it is possible to bind to the result of calling a method of an object via ObjectDataProvider, but your expression is a chain of method calls that I believe cannot be used to source ObjectDataProvider directly in XAML.
You should instead think about implementing a separated presentation pattern such as Model-View-ViewModel to expose the result of your expression via a collection property on a ViewModel that you then bind as the DataContext of your view (Window).
Something like:
MainWindow.xaml
<Window x:Class="WpfApplication10.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>
<ItemsControl ItemsSource="{Binding Items}"/>
</Grid>
</Window>
MainWindow.cs
using System;
using System.Collections.Generic;
using System.Windows;
using Microsoft.Win32;
namespace WpfApplication10 {
public class ViewModel {
public IEnumerable<String> Items {
get { return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(#"SOFTWARE\Vendor\Product\Systems").GetSubKeyNames(); }
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new ViewModel();
}
}
}

Related

Refactoring property name in code behind does not propagate to XAML file

I'm trying to refactor the property MyText to a new name HerText in the following solution:
MainWindow.xaml.cs
using System.Windows;
namespace resharper_refactoring_xaml
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyText = "Blabla";
DataContext = this;
}
public string MyText { get; set; }
}
}
MainWindow.Xaml
<Window x:Class="resharper_refactoring_xaml.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:resharper_refactoring_xaml"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding Path=MyText}"></TextBlock>
</Grid>
</Window>
I right click on the property and select Refactor this > Rename. Then I type in a new name for the property, hit Next.
Unfortunately, only the references of MyText in the code-behind are renamed. References to MyText in the XAML file rename intact.
According to this question Resharper should be able to propagate refactorings to XAML files.
Why is the rename not propagating to the XAML file? Is there some sort of Resharper setting I might have overlooked?
The reason behind this seems to be that ReSharper cannot determine that the property name specified in the XAML markup refers to the property defined in the MainWindow class, if the DataContext property is set in code-behind.
Bindings refer to the DataContext of controls as source by default. If it is not detected, the link between the loose markup and the defining type is lost. I cannot tell if this is a bug in ReSharper or a general limitation.
However, there are two simple solutions to this issue that work for me:
Set a design time data context to the type that defines the property here MainWindow.
<Window ...
d:DataContext="{d:DesignInstance Type={x:Type local:MainWindow}}">
Set the data context via binding in XAML instead of code-behind.
<Window ...
DataContext="{Binding RelativeSource={RelativeSource Self}}">

WPF Command binding to a static viewmodel

Firstly, I am new to WPF and MVVM, am trying hard to write well structured/separated code so please be kind.
I have created a user control and its own separate view model. In the view model I have an ICommand which relays to a method in the same viewmodel. I bind to this command in the XAML using System.Windows.Interactivity on an event like so:-
<UserControl x:Class="MyNamespace.MyControl"
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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MyNamespace"
mc:Ignorable="d"
Height="300"
d:DesignHeight="300" d:DesignWidth="1500"
IsManipulationEnabled="True"
Background="{StaticResource BackgroundWhiteBrush}">
<Grid
d:DataContext="{x:Static local:MyControlDesignModel.Instance}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseUp">
<i:InvokeCommandAction Command="{Binding MyViewModelCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
My code behind (which I'm trying to leave as empty as possible) looks like this:
namespace MyNamespace
{
/// <summary>
/// Interaction logic for MyControl.xaml
/// </summary>
public partial class MyControl : UserControl
{
public MyControl()
{
DataContext = ViewModelMyControl;
InitializeComponent();
}
}
}
I want to be able to use this control in several pages. I also want to be able to call a method in this view model (passing a parameter) from other view models to allow it to update itself from a datastore.
I used a DI container to provide a reference to the view model so that I can a) reference its data loading method from another place and b) set that to the DataContext in the code-behind (above).
The implementation of the DI container provides this as follows:
/// <summary>
/// A shortcut to access the <see cref="MyControlViewModel"/>
/// </summary>
public static MyControlViewModel ViewModelMyControl => Framework.Service<MyControlViewModel>();
With this DI referenced viewmodel on the DataContext, the event/command does not fire.
If I change the code behind to as follows, the event/command does fire but then I lose the static reference which I was trying to have to "hold" the data between pages. I seem to be able to have events or static reference but not both.
namespace MyNamespace
{
/// <summary>
/// Interaction logic for MyControl.xaml
/// </summary>
public partial class MyControl : UserControl
{
public MyControl()
{
DataContext = new MyControlViewModel();
InitializeComponent();
}
}
}
I think it has something to do with the ViewModel lifecycle or perhaps binding in general. I have been following a lot of guides and now find myself stuck.
How do I have event/commands firing and also maintain a reference to the user control's data between pages which use it?

Cannot access an XAML class-instance from code

I've created an class which will be the DataContext for my app, and instantiated it via XAML:
<Window x:Class="MyApp.UI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myDataModel="clr-namespace:MyApp.MyDataModel"
Title="MainWindow">
<Window.Resources>
<myDataModel:MyDataClass x:Name="the_DataModel" x:Key="a_DataModel"/>
</Window.Resources>
I want to act on this object in the constructor of my Window:
public MainWindow()
{
InitializeComponent();
the_DataModel.LoadFromFile(); // One of these *should* work!
a_DataModel.LoadFromFile();
}
However it seems that neither name (the_DataModel, nor a_DataModel) is a member of the Window class. When I type this., and use auto-complete, I cannot find anything that resembles the object I created in XAML.
How can I create an instance of a class in XAML, and access it in code?
Since you have added it as a resource in window resources, you can get it from Resource collection by indexing with resource key.
MyDataClass dataModel = (MyDataClass)Resources["a_DataModel"];
dataModel.LoadFromFile();

Window DataContext or ElementName

I am currently facing a little problem with specifing or not specifing the datacontext of a window, and why there is a difference between various methods. Hope you can help me out.
Lets start with some code to show my problem. This is the code behind for my TestWindow.xaml.cs, nothing really special about it just a simple string property
public partial class TestWindow : Window
{
private string _helloWorld = "Hello World!";
public string HelloWorld
{
get { return _helloWorld; }
set { _helloWorld = value; }
}
public TestWindow()
{
InitializeComponent();
}
}
This code will be the same for all 3 following XAML layouts, so no changes behind the code only in XAML.
1.) Databinding with given ElementName
<Window x:Class="Ktsw.Conx.ConxClient.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestWindow" Height="300" Width="300"
Name="TestWin">
<Grid>
<TextBlock Text="{Binding HelloWorld, ElementName=TestWin}"></TextBlock>
</Grid>
</Window>
2.) Databinding with specifing DataContext on Window
<Window x:Class="Ktsw.Conx.ConxClient.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestWindow" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<TextBlock Text="{Binding HelloWorld}"></TextBlock>
</Grid>
</Window>
3.) Neither ElementName nor specifing DataContext
<Window x:Class="Ktsw.Conx.ConxClient.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestWindow" Height="300" Width="300">
<Grid>
<TextBlock Text="{Binding HelloWorld}"></TextBlock>
</Grid>
</Window>
The first two methods work just fine, but why fails the 3rd?
In the first method I am not specifing the DataContext and it works automatically, in the second method I am not specifing the ElementName and it works, but without declaring one of those it fails. Why would it fail getting both automatically, but work fine with getting each individually?
Read the Remarks for the Binding.Source property. The binding source is the object on which to look up the property specified by Path (HelloWorld). ElementName, RelativeSource, and Source set the binding source. If they are null, the source is the DataContext of the element that you are binding (TextBlock). If an element's DataContext is not explicitly set, it's inherited from a containing element.
In the first case, any DataContext is irrelevant to the TextBlock binding, because the source is being set explicitly through ElementName.
In the second case, a DataContext is necessary to specify a source because it is not being set explicitly through ElementName, RelativeSource, or Source. TextBlock's DataContext is not set, so Window's is inherited.
In the third case, there is no explicit source and no DataContext to inherit, so the source is null and binding fails. There is no default source.
{Binding} by default always refers to the DataContext.
In your first example you simply deactivate the default behavior by using elementName, this uses the element as source then.
In your second example you explicitly change the DataContextto point at your Window.
So in the last example the {Binding HelloWorld} tries to convert null into and Object with a HelloWorld property which fails.
It is working in 1st and 2nd case because TextBlock is able to find HelloWorld property
In 1st case you are specifying ElemtnName so TextBlock will refer to Window and search for property HelloWorld in Window class.
In 2nd case you are specifying DataContext of Window so Textblock will inherit that and will look for HelloWorld property in DataContext i.e. again Window class
In 3rd case you aren't specifying anything so TextBlock is unable to find HelloWorld property
In 1st case I am not specifing a DataContext so why should it know
what to get?
When you are setting ElementName, TextBlock will try to find HelloWorld from that element
In 2nd case I am not specifing what it should get?
You are setting the DataContext of it's parent that is Window so it automatically inherits that and you are telling it to extract HelloWorld property from DataContext
The third fails because the DataContext is not inferred. If you wanted the third to work, you'd do something like this:
public TestWindow()
{
InitializeComponent();
this.DataContext = this;
}
It doesn't know anything about the path HelloWorld because it doesn't have a DataContext.

Restricting Input in WPF

I am creating a custom control that when invoked in the XAML can be set to only allow certain types of inputs:
<lib:CustomControl RestrictTo="UnsignedIntegersOnly" ... ></CustomControl>
Where the UnsignedIntegersOnly is part of an Enum containing the set of allowed restrictions.
If the user inputs something that is not allowed, the control will throw a validation error and not allow him to continue to the next form/page/etc.
My vision for implementing this, was to, in the underlying TextBox that makes up this control, bind its text field to a validation rule which will be passed as an input the RestrictTo value that was specified in the CustomControl XAML declaration. Then in that ValidationRule class, handle the RestrictTo specific validation and return whether the validation was successful or not.
This is where I am not quite sure how to proceed. Is it even possible to pass arguments to the ValidationRule in such a seemingly dynamic manner? I am setting a property, RestrictTo, of my control and then passing that to its validation.
If it is possible, how would it be done? What sort of binding or resource linking should I use?
You might be interested in using a MaskedTextBox control, it will restrict what the user can input in the TextBox.
As there's no official control from Microsoft for WPF I would suggest the following from Xceed :
MaskedTextBox (it's free to use :-)
Here you have the syntax of the mask :
http://msdn.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.mask.aspx
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="MainWindow" Height="350" Width="525">
<Grid>
<xctk:MaskedTextBox Mask="0000"></xctk:MaskedTextBox>
</Grid>
</Window>
If you have Visual Studio 2012 you can easily get this package through NuGet :
(right-click on your project )
Note : on my answer to your previous question, I extensively used validation rules in the link I posted but I'd say that if there are some times where you can avoid it through the means of a well-crafted component/control, then it's wise to do so. As #Barn pointed out on your previous question, a generic validation rule might be a hard thing to do and somewhat questionable as you'll have to handle all the types in it, IMO it's a little counter-intuitive as validators and converters are generally specific against being generalist; you're likely to waste more time on it than it's worth and it will be probably less re-usable than you think it could be. (source : my experience)
Below code should get you started. It is a user control and not a custom control. I recomend you get your code working as a user control first and then convert it to a custom control. Bind Valid property to some property in your viewmodel that controls user workflow.
XAML:
<UserControl x:Class="WpfApplication.ValidatingControl"
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" >
<StackPanel Orientation="Horizontal">
<TextBox Name="_textBox" TextChanged="OnTextChanged" Background="LightGray" Width="200"/>
<TextBlock Name="_messageText" Foreground="Red" />
</StackPanel>
</UserControl>
Code behind:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication
{
public partial class ValidatingControl : UserControl
{
public ValidatingControl()
{
InitializeComponent();
}
public enum Restrictions
{
UnsignedIntegersOnly,
SmallIntegersOnly
}
public static readonly DependencyProperty RestrictToProperty =
DependencyProperty.Register("RestrictTo", typeof(Restrictions), typeof(ValidatingControl), new PropertyMetadata(Restrictions.UnsignedIntegersOnly));
public Restrictions RestrictTo
{
get { return (Restrictions)GetValue(RestrictToProperty); }
set { SetValue(RestrictToProperty, value); }
}
public bool Valid
{
get { return (bool)GetValue(ValidProperty); }
set { SetValue(ValidProperty, value); }
}
public static readonly DependencyProperty ValidProperty =
DependencyProperty.Register("Valid", typeof(bool), typeof(ValidatingControl), new UIPropertyMetadata(false));
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
ValidateText(RestrictTo, _textBox.Text);
}
private void ValidateText(Restrictions restrictTo, string text)
{
// validate text, update _messageText, update Valid
}
}
}

Categories