How to bind to ItemsSource in DataTemplate? - silverlight

in my Silverlight 4 application, I want to use an AutoCompleteBox from the Silverlight Toolkit. I use this AutoCompleteBox in a listbox, which items are defined in a DataTemplate
<ListBox x:Name="ListBoxCharacteristics">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="{StaticResource SolidBrushVeryLightGrey}">
<sdk:AutoCompleteBox Text="{Binding Name, FallbackValue=[None], Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" IsTextCompletionEnabled="True"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
To provide the suggested items to the AutoCompleteBox, I need to bind it on the ItemsSource property. The idea was to create the list in the constructor and then bind it to the AutoCompleteBox. But the AutoCompleteBox is just in the DataTemplate, so I cannot reference it directly.
Any idea, how to achieve that? I thought about something like "ItemsSource="{Binding SuggestionList"} but that would mean I'd need to create this list as a Property for the class of the objects that I bind to the list, which would be a big overhead.
Thanks in advance,
Frank

I subscribed to the GotFocus-Event of the AutoCompleteBox and bind the list there. Thanks to Nathan and Shelby for putting my head towards the right direction!

You should be able to traverse the tree by referencing the listbox in code something like:
(ListBoxCharacteristics.ItemTemplate.VisualTree as AutoCompleteBox).ItemSource = your_new_list;
but you might be better off creating the Binding in that constructor:
Binding B = new Binding();
B.Mode = BindingMode.TwoWay;
B.NotifyOnValidationErrors = true;
B.FallbackValue = "[None]"; // not sure about this one
B.ValidatesOnExceptions = true;
B.Source = your_new_list;
(ListBoxCharacteristics.ItemTemplate.VisualTree as AutoCompleteBox).SetBinding(AutoCompleteBox.TextProperty, B);
ListBoxCharacteristics.ItemTemplate.VisualTree should give you that the root node of your ItemsTemplate and you should be able to cast that object to your AutoCompleteBox. If you had further embedded elements you would want to cast and attempt to get a container property for that element to continue further down into the template.

try this. This has worked for me a dozen times.
AutoCompleteBox autoComplete = Listbox.ItemTemplate.GetVisualDescendants().OfType<AutoCompleteBox>().SingleOrDefault();
autoComplete.ItemsSource = theListYouHavePopulated;
that is, of course, if there is only one AutoCompleteBox in the listbox template, if it comes first, then try,
FirstOrDefault();
at the end of your query.
Let me know if you need anything else.

You can set the ItemsSource property of the AutoCompleteBox in a handler of its Loaded event (you'll get the AutoCompleteBox itself as the sender of the event).
xaml:
<sdk:AutoCompleteBox ...
Loaded="autoCompleteBox_Loaded"/>
code behind:
private void autoCompleteBox_Loaded(object sender, RoutedEventArgs e)
{
var autoCompleteBox = sender as AutoCompleteBox;
autoCompleteBox.ItemsSource = SuggestionList; //the list you want to bind to
}
Hope this helps

Related

Binding inside ContentControl not working

I'm building a graphical designer, based upon an article by Sukram in CodeProject. I'm now trying to extend it so that each item on the canvas binds to a different ViewModel object - i.e. I'm setting the DataContext for each item.
Every item on the designer is actually a ContentControl, into which is placed a different template (based upon which toolbox item was dragged onto the canvas). So I have a template containing a TextBox, and I have a ViewModel object containing a Name property, and I bind the Text property of the TextBox to the Name property of the ViewModel, and ... nothing. I've checked the visual tree with Snoop, and it confirms that the DataContext of the TextBox is the ViewModel object. Yet the TextBox remains empty. And if I modify the (empty) Text in the TextBox, the Name property in the ViewModel does not change. So it looks like the binding is not being applied (or has been removed somehow).
I've found a few posts which talk about the ContentControl messing around with the DataContext and Content properties, but I'm not sure how applicable they all are. The code sets the ContentControl.Content as follows:
newItem = new ContentControl();
ControlTemplate template = toolbox.GetTemplate();
UIElement element = template.LoadContent() as UIElement;
ViewModelItem viewModel = new ViewModelItem() { Name = "Bob" };
newItem.Content = element;
newItem.DataContext = viewModel;
and the XAML for the template is:
<ControlTemplate>
<Border BorderBrush="Black" BorderThickness="1" Width="100">
<TextBox Text={Binding Name}/>
</Border>
</ControlTemplate>
Snoop shows that the TextBox has a DataContext, and if I Delve that DataContext I can see that it has a Name property whose value is "Bob". So why does the TextBox remain empty? Snoop allows me to change that Name property, but the TextBox remains empty.
What am I doing wrong?
A few more details. I've set the VS2010 Debug DataBinding option for the OutputWindow to Verbose, which seems to show that the binding is all being attempted before I set the DataContext. Is it possible that the change to the DataContext is not being recognised?
I've just found this post DataTemplate.LoadContent does not preserve bindings - apparently DataTemplate.LoadContent does not preserve bindings. So it looks like I have to write my own version of LoadContent().
I've realised that the template has come through a XamlWriter, which apparently strips all bindings. This wouldn't be helping.
I've not been able to fix the DataTemplate.LoadContent(), but I realised that I didn't actually need a DataTemplate, since the XamlWriter / XamlReader was already instantiating the UI element that I was after. I found a fix to make the XamlWriter write all the bindings here, and after that it all works.
Thanks for your help.
Maybe you need to tell the binding in the ControlTemplate to look at the TemplatedParent, as is mentioned in this thread?
<TextBox Text="{Binding Path=Name, RelativeSource={RelativeSource TemplatedParent}}"/>
Either that, or try to use a DataTemplate instead.
I can't test this at the moment, so I might just be guessing here.
I would use a DataTemplate, as bde suggests.
You are trying to put some UI on your own data (ViewModel), and this is what Data-Templates are meant for (ControlTemplate is usually what you use if you want to change how e.g. a Button looks).
Change your code to use ContentControl.ContentTemplate with a DataTemplate:
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Width="100">
<TextBox Text={Binding Name}/>
</Border>
</DataTemplate>
Code-behind:
newItem = new ContentControl();
//NOTE: .GetTemplate() needs to return a DataTemplate, and not a ControlTemplate:
newItem.ContentTemplate = toolbox.GetTemplate();
ViewModelItem viewModel = new ViewModelItem() { Name = "Bob" };
newItem.Content = viewModel;
newItem.DataContext = viewModel;

How to Aceess cell level ComboBox in WPF DataGrid?

My data grid column template which has combo box in it is as below .
<my:DataGridTemplateColumn x:Name="dgColReferece" Header="References" >
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cmbReferece_SRV" Loaded="cmbReferece_SRV_Loaded" Width="160" SelectionChanged="cmbReferece_SRV_SelectionChanged"
IsTextSearchEnabled="True" SelectedValue="{Binding Reference, Mode=TwoWay}" >
</ComboBox>
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>
All combo boxes have Add new as one item in them which has value -2. When the user clicks on add new a new item added to the database and should be rebound to all comboboxes in the grid.
Below is my code behind for SelectionChanged
private void cmbReferece_SRV_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ ComboBox objComboBox = (ComboBox)sender;
if (objComboBox.SelectedValue.ToString() == "<-- Add New -->")
{
//code for saving new item entered by user to database
if (IsSaved)
{
DataSet dsReference = (DataSet)GetFStdReference();
CommonCalls.BindDropDownList(cmbReferece_SRV, dsReference.Tables[0], "Reference", "Reference");
}
objComboBox.SelectedValue = -1;
}
}
This will bind the new item only to the combobox in the selected row. But I need it to bind to all comboboxes? How Can I do this. I am new to wpf and binding stuffs > How can i Proceed ?
You code above is a little confusing. Can you explain more what are you trying to do. I can see several deviations from the proper WPF programming practises esp. regarding using comboboxes in datagrid.
E.g.
Why are you using events like cmbReferece_SRV_SelectionChanged and not using SelectedValue and SelectedValuePath via Converter?
Also what is your ComboBox.ItemsSource? DataTable? List of objects?
Why are you setting ItemsSource of a ComboBox in its own SelectionChanged event, which is counterproductive.
I understand that you are new to WPF, so may be if you explain your problem to me, I can suggest some useful WPF practises of coding for your issue.

How to make a WPF DataGrid display a ViewModel collection with binding and a DataTemplate

First off, an apology as I am sure this is question's answer is quite simple. Nonetheless I still cannot seem to find an answer.
This is my first week using WPF. I simply wish to display the contents of some sort of list inside of a DataGrid. I am currently trying to use an ObservableCollection<> and a DataGrid, but that can change. How do I DataTemplate the list and display it?
The list is in a ViewModel, which has been set in Apps.xaml.cs as the DataSource for the MainWindow.xaml:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var window = new MainWindow();
// Create the ViewModel to which
// the main window binds.
var viewModel = new MainWindowViewModel();
// Allow all controls in the window to
// bind to the ViewModel by setting the
// DataContext, which propagates down
// the element tree.
window.DataContext = viewModel;
window.Show();
}
Here is the current list:
public ObservableCollection<PersonData> SearchResults { get; set; }
As for my xaml, though, I am rather lost -- I've tried fiddling around with binding and ItemsSource, and really have no idea how to use them here. Also, I suppose using a DataTemplate will be necessary, as I need to let it know somehow that the columns need to display certain properties of the PersonData, such as first and last name, location, and title. Any help is appreciated.
EDIT
More generally -- how does one simply display a ViewModel list, collection, what have you, period?
Your basic DataGrid syntax should look something like this:
<DataGrid ItemsSource="{Binding SearchResults}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" />
</DataGrid.Columns>
</DataGrid>
If you don't want to display your data in a DataGrid, you can use an ItemsControl.
The default ItemsControl will add all your items to a StackPanel, and draw each of them using a TextBlock bound to the .ToString() of the item. You can change how the ItemsControl displays the items by specifying your own ItemsPanelTemplate and ItemTemplate for it to use. For some examples, check out my blog post on WPF's ItemsControl.
Ach. Failure to pre-study is fatal -- I did not implement INotifyPropertyChanged. Found out how to here: http://msdn.microsoft.com/en-us/library/ms743695

Setting the objectinstance to the current data item

I am fairly new to WPF, have been working on finding an answer to this for a couple days without much luck, it seems like there should be a way. I have set up a DataTemplate whose DataType is a custom class of mine. Within the DataTemplate definition, I have set up a resources collection using . I did this because I want to create an ObjectDataProvider that will be available to the controls in the DataTemplate - I want the ObjectInstance of this ObjectDataProvider, to be currently bound data item (teh current instance within a list, of my custom class) - because then I want to be able to run a method on the current data instance - when the user changes the text in a textbox that is part of the DataTemplate. Hard to explain but this should make it clearer, here is my xaml:
<DataTemplate x:Key="TierDisplay" DataType="{x:Type tiers:PopulatedTier}">
<DataTemplate.Resources>
<ObjectDataProvider x:Key="FilteredItems" MethodName="GetDisplayItems">
<ObjectDataProvider.MethodParameters>
<sys:Int32>0</sys:Int32>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</DataTemplate.Resources>
<StackPanel Orientation="Vertical">
<TextBox Name="txtMaxSupplyDays" LostFocus="txtMaxSupplyDays_LostFocus"></TextBox>
<DataGrid ItemsSource="{Binding Source={StaticResource FilteredItems}}" />
</StackPanel>
</DataTemplate>
Each instance of the DataTemplate is bound to an instance of the PopulatedTier class. When the user leaves the textbox, txtMaxSupplyDays, I have code in the code-behind to take the value they have entered, and put it into the first MethodParameter of my ObjectDataProvider (whose key is FilteredItems). This works fine using the C# code-behind below, the code finds FilteredItems and plugs the desired value into the MethodParameter. But I can't figure how to tie FilteredItems into the current instance of PopulatedTier so that its GetDisplayItems will run. (If this worked, then presumably the DataGrid would refresh, using the output of GetDisplayItems as its ItemsSource.) In fact, in the C# below, it finds/recognizes the DataContext property of the textbox (sender) as being an instance of PopulatedTier. But how can I refer to this in the XAML within the ObjectDataProvider definition? THANK YOU and let me know if I can clarify further. Of cousre alternate suggestions are welcome; I'd like to keep as much in the XAML and out of the code-behind as I can.
private void txtMaxSupplyDays_LostFocus(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
int value;
bool valueOK = Int32.TryParse(textBox.Text, out value);
if (valueOK)
((ObjectDataProvider)textBox.FindResource("FilteredItems")).MethodParameters[0] = value;
}
You have right thoughts about your code-behind - it have to be as small as possible. Its one of the slogan of MVVM pattern, that is what you need - learn MVVM. Internet have a lot of resources, so it wouldn't be a problem to find it.

Binding in PrepareContainerForItemOverride method

I try to implement PrepareContainerForItemOverride method of ItemsControl. It will put items to TextBox. It works nice, but how can I binding an item to the textbox text property? One way mode works nice, but when I want two way mode, I have to know the path.
Here is my code:
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
if (element is TextBox)
{
//((TextBox)element).Text = (string)item;
Binding binding = new Binding("I don't know what should i write here.");
binding.Mode = BindingMode.TwoWay;
((TextBox)element).SetBinding(TextBox.TextProperty, binding);
}
}
Thank you for your help!
If the commented line in the code in your question is what you have before then it indicates that the type of item you are providing is String. Two way binding on a string makes no sense the binding would not know where to assign the new value.
The type of items being displayed would need to be some object that has a property of type String, it would be the name of this proprerty that you pass to the Binding constructor.
That said its not clear why you would even need to sub-class ItemsControl in this way. Why not:-
<ItemsControl ItemSource="{Binding SomeEnumberableOfObjectsThatHaveASomeStringProperty}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Test="{Binding SomeString, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Resources