How can I set a binding to a Combox in a UserControl? - wpf

I have spent several days on this issue and can't seem to get it to work.
I have a user control that is saved out to a xaml file with the following code:
StringBuilder outstr = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
XamlDesignerSerializationManager dsm = new
XamlDesignerSerializationManager(XmlWriter.Create(outstr, settings));
dsm.XamlWriterMode = XamlWriterMode.Expression;
System.Windows.Markup.XamlWriter.Save(test1, dsm);
String saveCard = outstr.ToString();
File.WriteAllText("inputEnum.xaml", saveCard);
Xaml for the user control:
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding DescriptionWidth}" />
<ColumnDefinition Width="{Binding ValueWidth}" />
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="1" Background="White" FontSize="{Binding FontSizeValue}" Width="Auto"
Padding="10,0,5,0" ItemsSource="{Binding ComboItemsProperty}" SelectedIndex="{Binding EnumSelectedIndex}">
</ComboBox>
</Grid>
The ItemsSource in the combobox is what is giving me problems.
When I save an instance of this usercontrol out to a file, from my understanding, the {Binding ComboItemsProperty} is lost. So, in the constructor of my usercontrol I have:
public UserInputEnum()
{
InitializeComponent();
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this;
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBinding(ComboBox.ItemsSourceProperty, bind);
}
Here is my property and the changed method:
EnumItemsCollection ComboItems = new EnumItemsCollection();
public EnumItemsCollection ComboItemsProperty
{
get { return ComboItems; }
set
{
ComboItems = value;
OnPropertyChanged("ComboItemsProperty");
}
}
public void OnPropertyChanged(string propertyName)
{
getEnumItems(this.ComboItemsProperty, this.EnumSelectedIndex, this.ID, this.SubmodeID);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this.ComboItems, new PropertyChangedEventArgs(propertyName));
}
}
Just a note. EnumItemsCollection is a simple class that inherits off ObservableCollection. There is nothing else to this class. (not sure if this makes a difference).
I think this should work but when when I load the XAML file through the XAMLReader, my combobox items won't update.
EDIT:
I ran a little test on an instance of user control that wasn't loaded from XAML but is in the MainWindow.xaml.
Everything works fine. When I add to the ComboItemsProperty, the combobox updates.
So, I took away the {Binding ComboItemsProperty} and tried to set the binding in the code as above changing 'this' to the instance of the user control. Didn't work. This tells me it is the binding code that is not functioning correctly.
I'm fairly certain is the bind.Source line that is the issue. When it is in a UserControl I am unsure of what to put there.
EDIT:
Code that loads usercontrol from file:
FileStream stream = File.Open("usercontrol.xaml", FileMode.Open, FileAccess.Read);
ComboBox cmb = System.Windows.Markup.XamlReader.Load(stream) as ComboBox;
It loads perfectly fine. The Binding just isn't working(ItemsSource={Binding ComboItemsProperty}) because Bindings aren't saved out.
I load it from a file because this program will have many User Interfaces in a sense. Each one will be loaded by a different person using the program.

You need to the set context of the instance containing your property ComboItemsProperty. So instead of 'this' u should set it to this.DataContext or other class object instance containing the ItemSource property you have defined..
Try this,
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this.DataContext;
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBinding(ComboBox.ItemsSourceProperty, bind);
Update
According to Serialization Limitations of XamlWriter.Save available on msdn,
Many design-time properties of the original XAML file may already be optimized or lost by the time that the XAML is loaded as in-memory objects, and are not preserved when you call Save to serialize.
Common references to objects made by various markup extension formats, such as StaticResource or Binding, will be dereferenced by the serialization process.
Conclusion, that I made out now is you cannot directly load the UserControl as whole by Serialization - Deserialization procedure of XAML. I think you can load the object instances by Serialization - Deserialization procedure on the DataContext of the UserControl i.e. the custom list(s) or object(s) you have databound.

Related

Bind the tab header to a property from code

I am binding a collection to a TabControl using its ItemSource property.
I'm programming WPF in code and not in XAML to get a deeper understanding.
The problem I'm faced with is that if I want to bind the header of a TabItem to a property ("EntityID") the binding does not kick in.
The code works if I set a value instead of a binding (code below in comments)
var binding = new Binding();
binding.Path = new PropertyPath("EntityID");
DataTemplate itemTemplate = new DataTemplate();
var label = new FrameworkElementFactory(typeof(Label));
//label.SetValue(Label.ContentProperty,"test");
label.SetBinding(Label.ContentProperty, binding);
itemTemplate.VisualTree = label;
_tabControl.ItemTemplate = itemTemplate;
Furthermore, if set the ContentTemplate instead of the ItemTemplate the binding works as well.
How can I bind the tab header to a property of my ItemsSource from purely code?
There are many ways to set bindings from Code Behind. What you should try to bind is the HeaderProperty on the TabItem. Though you must first retrieve it to do that.
Here is a working example that should set you started. It's not the way I would do it, as I would do that in xaml, but as you requested to do that from code behind, here you go :)
On a side note, it's almost always a bad idea to define templates in Code behind, try to avoid it as much as possible.
Windows.xaml
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:StackOverflow"
Title="Super long title of the super window" Width="500" Height="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Entity}">
<TextBlock Text="{Binding Id}" FontSize="{Binding Size}" />
</DataTemplate>
</Window.Resources>
<local:MyTabControl x:Name="tabControl" ItemsSource="{Binding Entities}" />
</Window>
Window.xaml.cs
using System.Windows;
using System;
using System.Windows.Data;
using System.Collections.Generic;
using System.Windows.Controls;
namespace StackOverflow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public IEnumerable<Entity> Entities
{
get
{
for (int i = 0; i < 5; i++)
{
yield return new Entity() { Id = i };
}
}
}
}
public class MyTabControl : TabControl
{
protected override DependencyObject GetContainerForItemOverride()
{
var tabitem = base.GetContainerForItemOverride() as TabItem;
if (tabitem != null)
{
tabitem.Loaded += OnTabItemLoaded;
}
return tabitem;
}
void OnTabItemLoaded(object sender, RoutedEventArgs e)
{
var tabItem = sender as TabItem;
if (tabItem == null)
throw new InvalidOperationException();
tabItem.SetBinding(TabItem.HeaderProperty, new Binding("DisplayName"));
}
}
public class Entity : DependencyObject
{
public int Id { get; set; }
public string DisplayName { get { return "Entity ID : " + Id; } }
}
}
Couple of things...
As a WPF designer and developer, XAML is the best way of GUI and Code
Behind segregation. It does not decrease my understanding of WPF in
any way. So I recommend XAML.
Believe me being a Winforms / ASP.NET developer myself, I was initially reluctant of using XAML, but the titanic amount of code that I had to write and the relationships between various GUI elements and upon that taming the beasts called Templates \ Styles \ Triggers and ResourceDictionaries just using C# code behind was just a plain torture to me.
Enough of my experience, this is about your issue!!
To answer your question, have you set _tabControl.ItemsSource? And make sure that each item from that ItemsSource has EntityID property in it. Your code should work.
If this still doesnt work then try to see in your Visual Studio's Output window, if there are any binding errors.

How can I bind a ComboBox that is loaded from a file?

Here is my situation. I have multiple user controls in a canvas. This canvas is saved out to a xaml file using XamlWriter. As most of you know, bindings are not saved using this method, so when I use XamlReader and read the user control back in, the binding no longer exists.
For a simple test, I have been trying to re-bind a ComboBox ItemsSource loaded from a XAML file (which is what I'm having issues with inside the User Control). I have tried implementing INotifyPropertyChanged, however, my variable:
public event PropertyChangedEventHandler PropertyChanged
is always null when I try and set the ComboItemsProperty:
public ObservableCollection<string> ComboItemsProperty
{
get { return ComboItems; } //Field
set
{
ComboItems = value;
OnPropertyChanged("ComboItemsProperty");
}
So, my ultimate goal is to load a xaml file and then add items to a ComboBox's ItemsSource and then have the ComboBox update with the new items.
Am I going about this the wrong way? Could someone provide me with a simple working example of this?
EDIT:
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged
if (PropertyChanged != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm fairly certain it has something to do with loading the XAML and the binding no longer being set. I have tried setting the binding, but no luck.
2nd EDIT:
I think I'm 99% sure that the binding is the cause. My OnPropertyChanged works fine as long as I didn't load the combobox from a file. I have tried setting the binding as follows:
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this.ComboItemsProperty; //Not sure about this line.
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBiding(ComboBox.ItemsSourceProperty, bind);
Confirmed. When I bring back in a simple combobox, my attempt to bind it is not working. It must be something in the code above.
bind.Source needs to point to the object containing ComboItemsProperty, not the property itself.
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this;
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBiding(ComboBox.ItemsSourceProperty, bind);
You can check the binding succeeded with:
if (GetBindingExpression(ComboBox.ItemsSourceProperty).Status != BindingStatus.Active)
{
//binding didn't work
}

TabControl ItemTemplate without ItemsSource

I am doing a WPF application with a TabControl. At the beginning I had a TabControl bound to ObservableCollection of TabBase items, where TabBase is a base class for tab viewmodel:
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Tabs}"
ItemTemplate="{StaticResource ClosableTabTemplate}"
...
public ObservableCollection<TabBase> Tabs { get; private set; }
...
public abstract class TabBase : ViewModelBase
...
public abstract class ViewModelBase : INotifyPropertyChanged
{
public virtual string DisplayName { get; protected set; }
...
<DataTemplate x:Key="ClosableTabTemplate">
<DockPanel Width="120">
<Button
Command="{Binding Path=CmdClose}"
Content="X"
/>
<ContentPresenter
Content="{Binding Path=DisplayName}">
</ContentPresenter>
</DockPanel>
</DataTemplate>
But I've faced with an issue when I switch tabs it looks like current tab is being created each time, even if it was already opened before. Searching thru StackOverflow I've found the solution here with reference to here. I've replaced using of declarative ItemsSource with dynamic creation of tabs from code. Tabs switching performance issue was resolved, but tab headers have lost link to template, so instead of tab header with caption and close button I see just a little tab header without anything. Playing a bit with tab creation code, I was able to restore tab size and close button, but without binding - there is no caption and close button doesn't work (5 lines with item.Header restored original tab size):
private void AddTabItem(TabBase view)
{
TabItem item = new TabItem();
item.DataContext = view;
item.Content = new ContentControl();
(item.Content as ContentControl).Focusable = false;
(item.Content as ContentControl).SetBinding(ContentControl.ContentProperty, new Binding());
item.Header = new ContentControl();
(item.Header as ContentControl).DataContext = view;
(item.Header as ContentControl).Focusable = false;
(item.Header as ContentControl).SetBinding(ContentControl.ContentProperty, new Binding());
item.HeaderTemplate = (DataTemplate)FindResource("ClosableTabTemplate");
tabControl.Items.Add(item);
}
The question is, how can I make ItemTemplate working for TabControl without ItemsSource binding?
When you explicitly set your item.Header to a ContentControl, the HeaderTemplate is now using that object as its DataContext. Normally, the Header property would get your ViewModel and a ContentPresenter would take that (non-Visual) object and apply the HeaderTemplate to it. You've now pushed your ViewModel down a level in the hierarchy so the template is not being applied at the same place as the data. Moving either one should fix the Binding issues but one or the other may work better for your situation:
item.Header = view;
or
(item.Header as ContentControl).ContentTemplate = (DataTemplate)FindResource("ClosableTabTemplate");

WPF DataGrid multiselect binding

I have a datagrid that is multi-select enabled. I need to change the selection in the viewmodel. However, the SelectedItems property is read only and can't be directly bound to a property in the viewmodel. So how do I signal to the view that the selection has changed?
Andy is correct. DataGridRow.IsSelected is a Dependency Property that can be databound to control selection from the ViewModel. The following sample code demonstrates this:
<Window x:Class="DataGridMultiSelectSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="Window1" Height="300" Width="300">
<StackPanel>
<tk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding}" EnableRowVirtualization="False">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Value" Binding="{Binding Value}" />
</tk:DataGrid.Columns>
<tk:DataGrid.RowStyle>
<Style TargetType="tk:DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</tk:DataGrid.RowStyle>
</tk:DataGrid>
<Button Content="Select Even" Click="Even_Click" />
<Button Content="Select Odd" Click="Odd_Click" />
</StackPanel>
</Window>
using System.ComponentModel;
using System.Windows;
namespace DataGridMultiSelectSample
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new[]
{
new MyViewModel {Value = "Able"},
new MyViewModel {Value = "Baker"},
new MyViewModel {Value = "Charlie"},
new MyViewModel {Value = "Dog"},
new MyViewModel {Value = "Fox"},
};
}
private void Even_Click(object sender, RoutedEventArgs e)
{
var array = (MyViewModel[]) DataContext;
for (int i = 0; i < array.Length; ++i)
array[i].IsSelected = i%2 == 0;
}
private void Odd_Click(object sender, RoutedEventArgs e)
{
var array = (MyViewModel[])DataContext;
for (int i = 0; i < array.Length; ++i)
array[i].IsSelected = i % 2 == 1;
}
}
public class MyViewModel : INotifyPropertyChanged
{
public string Value { get; set; }
private bool mIsSelected;
public bool IsSelected
{
get { return mIsSelected; }
set
{
if (mIsSelected == value) return;
mIsSelected = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Be sure to set EnableRowVirtualisation="False" on the DataGrid element, else there's a risk that the IsSelected bindings fall out of kilter.
I haven't worked with the DataGrid much, but one technique that works for the ListView is to bind to the IsSelected property of the individual ListViewItem. Just set this to true for each object in your list, and then it will get selected.
Maybe the object that represents a row in the DataGrid also has an IsSelected property, and can be used in this way as well?
Guys, thanks for the help. My problem was solved. I think the problem is pretty common for new WPF developers, so I will restate my problem and as well as the solution in more details here just in case someone else runs into the same kind of problems.
The problem: I have a multi-select enabled datagrid of audio files. The grid has multiple column headers. The user can multi-select several row. When he clicks the Play button, the audio files will be played in the order of one the columns headers (say column A). When playback starts, the multi-select is cleared and only the currently playing file is highlighted. When playback is finished for all files, the multi-selection will be re-displayed. The playback is done in the viewmodel. As you can see, there are two problems here: 1) how to select the currently playing file from the viewmodel, and 2) how to signal to the view from the viewmodel that playback is finished and re-display the multi-selection.
The solution: To solve the first problem, I created a property in the viewmodel that is bound to the view's SelectedIndex property to select the currently playing file. To solve the second problem, I created a boolean property in the view model to indicate playback is finished. In the view's code behind, I subscribed the the boolean property's PropertyChanged event. In the event handler, the view's SelectedItems property is re-created from the saved multi-selection (the contents of SelectedItems was saved into a list and SelectedItems was cleared when playback started). At first, I had trouble re-creating SelectedItems. It turned out the problem was due to the fact that re-creation was initiated through a second thread. WPF does not allow that. The solution to this is to use the Dispatcher.Invoke() to let the main thread do the work. This may be a very simple problem for experienced developers, but for newbies, it's a small challenge. Anyway, a lot of help from different people.
Just use SelectedItems on any MultiSelector derived class , and use methods Add, Remove, Clear on IList it returns .

WPF MVVM User Control binding issues

I have an application that uses MVVM. I have several items on the main window that bind to the ViewModel for that window. When I run it everything works. However, when I add a user control to the main window and try to bind to one of its dependency objects it throws an exception (“Object reference not set to an instance of an object”). The exception window just pops up on the screen and does not link to any particular place in code. And any other information in the exception is not helpful.
I’ve tried my best to trace this down but I’m not having any luck. In the constructor of window I’ve checked and verified that the item that it’s attempting to bind to exists and is an object (int[]). I’ve also manually set the property in the constructor with problems.
Here are some code snippets if anyone can notice anything.
Here is where I use the user control and attempt to bind to the 'view' property
<local:Histogram Grid.Row="2" Grid.ColumnSpan="2"
View="{Binding Path=HistogramData}"
Foreground="{DynamicResource FontColor}"
BucketStroke="{DynamicResource BucketStrokeBrush}"
BucketFill="{DynamicResource BucketFillBrush}"
SelectedBrush="{DynamicResource FamilyEditListViewSelectedBrush}"
DisabledForegroundBrush="{DynamicResource DisabledForegroundBrush}"
AxisBrush="{DynamicResource AxisBrush}"
MaxHeight="130" />
Here is the field in the view model that I am attempting to bind to:
public int[] HistogramData
{
get
{
return histogramData;
}
set
{
if (value != histogramData)
{
histogramData = value;
RaisePropertyChanged("HistogramData");
}
}
}
And in the constructor of the view model I instantiate the object
histogramData = new int[256];
And finally here is the view property in the user control
public static readonly DependencyProperty ViewProperty =
DependencyProperty.Register("View",
typeof(int[]),
typeof(Histogram),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(ViewProperty_Changed)));
public int[] View
{
get { return (int[])GetValue(ViewProperty); }
set { SetValue(ViewProperty, value); }
}
I don't know if this is enough information to solve anything so if more code is req please let me know. I could also zip up the project if someone is so inclined to look at that. Thanks in advance.
You could try initialising the array when you initialise FrameworkPropertyMetaData on the dependency property.
new FrameworkPropertyMetadata(new int [256],
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(ViewProperty_Changed))
I think that the program might be hitting a null reference exception before it manages to bind the dependency property to the viewmodel property.
Ok I've had a look at your example project and think i have a solution.
change the int[] in the viewmodel to a List<int>.
I'm not sure why this works. I hope there is no technical reason that list<int> is not suitable for you.
Here is what I have changed in the solution
in the viewmodel
public List<int> CustomData
{
get
{
return new List<int>(){0,1,2,3};
}
set
{
}
}
In the arraycontrol codebehind
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data",
typeof(List<int>),
typeof(ArrayControl),
new FrameworkPropertyMetadata(new List<int>()));
public List<int> Data
{
get { return (List<int>)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
In arraycontrol.xaml. Just added listbox to show data binding working
<UserControl x:Class="UserControlWithArray.Controls.ArrayControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<TextBlock x:Name="MessageTextBlock" Text="ArrayControl"/>
<ListBox ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Data}"/>
</Grid>
</UserControl>
Use the debugger to get an exception stack trace. That should help you narrow the problem down.
There are several ways to do this. For example, you should be able to just view the details of the exception. Or you could open a watch window and enter the expression $exception and then hit evaluate.

Resources