I've looked around, found some things and now stuck on a combobox with two columns displayed in the drop-down area. I have a xaml themes available and the combobox "Style" is defined and works well throughout as expected, so that part is ok.
Now, I have a combobox that I need to have display two values, think of it as State Abbreviation and State Name for the drop-down, coming from a DataTable.DefaultView binding source for the items.
If I have
<my:cboStates TextSearch.TextPath="StateAbbrev">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" TextSearch.Text="{Binding Path=StateAbbrev}">
<TextBlock Text="{Binding Path=StateAbbrev}"/>
<TextBlock Text="{Binding Path=FullStateName}" Margin="10 0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</my:cboStates>
this works. Now, how/where I'm stuck... Now, I want this same functionality on say 5 different forms and all to have the same content displayed, and if ever changed (not this, but for other multi-column comboboxes), I don't want to have to keep putting this directly in the form's XAML file.
I was hoping to put into a Theme's Resource Dictionary file and just keep reusing that "style" over and over. Makes sense. However, when I do, and the binding is to the data table, the only results I get when trying to do as a Style is the dropdown shows values of
System.Data.DataRowView
System.Data.DataRowView
System.Data.DataRowView
System.Data.DataRowView
instead of the actual 2 columns.
Here is what I have in the "theme" resource dictionary.
<DataTemplate x:Key="myStateComboTemplate" >
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Path=StateAbbrev}"/>
<TextBlock Text="{Binding Path=FullStateName}"/>
</StackPanel>
</DataTemplate>
<Style x:Key="StyleMyStatesCombobox" TargetType="{x:Type ComboBox}"
BasedOn="{StaticResource MyOtherWorkingComboBoxStyle}" >
<Setter Property="TextSearch.TextPath" Value="{Binding Path=StateAbbrev}" />
<Setter Property="ItemTemplate" Value="{StaticResource myStateComboTemplate}" />
</Style>
So, If I have TWO instances my "cboStates" class created on the form, and set one to the explicit styling listed first, and the SECOND based on the "Style" setting, the second one fails by only showing the repeated System.Data.DataRowView entries, not the actual data content.
What am I missing.
So, to clarify what I'm looking for...
States... ex data
AL Alabama
AK Alaska
AZ Arizona
AR Arkansas
CA California
CO Colorado
CT Connecticut
DE Delaware
I want the combobox to be displaying the abbreviated
AL, AK, AZ, etc and narrower combobox. This will ALSO be the "SelectedValue" upon return.
The actual Dropdown would present the data as listed above showing BOTH the abbreviation AND the long description of the state.
Sample of desired combobox
FINALLY got it working... and for those attempting similar. Since I was trying to have a standard "class" instance that could be used throughout, but not wanting to explicitly hard-reference the XAML in each page, part of the styling had to be handled during the actual in-code class instance.
Since I don't exactly know how/when where .net framework builds out all its controls, style assigments, etc, I was getting frustrated that it would work if direct from xaml, but fail when in code. So, I ended up FORCING the item template AND TextSearch.TextPath values in the code. Here's a short snippet of the class
public class myStatesCombo : ComboBox
{
public myStatesCombo()
{
Loaded += myAfterLoaded;
}
protected static DataTable myTableOfStates;
public void myAfterLoaded()
{
if( myTableOfStates == null )
myTableOfStates = new DataTable();
CallProcedureToPopulateStates( myTableOfStates );
ItemsSource = myTableOfStates.DefaultView;
// AFTER the object is created, and all default styles attempted to be set,
// FORCE looking for the resource of the "DataTemplate" in the themes.xaml file
object tryFindObj = TryFindResource("myStateComboTemplate" );
if( tryFindObj is DataTemplate )
ItemTemplate = (DataTemplate)tryFindObj;
// NOW, the CRITICAL component missed in the source code
TextSearch.SetTextPath( this, "StateAbbrev" );
}
}
Now, a special note. In the routine I used to populate the DataTable, I pre-check if the table exists or not. First time in, I create the table. If I need to re-populate it, if I just keep doing a "new DataTable" each time, it blows away the data/item template bindings. To PREVENT that, I would then do
if( myTableOfStates.Rows.Count > 0 )
myTableOfStates.Rows.Clear();
THEN, I call my
Sqlexecute call to query from the database (DataAdapter) and Fill() the datatable.
So, now all appears to be populated correctly, bindings for display and textsearch complete and ready to go.
Related
My problem is basically described in the title of this topic.
I have a datagrid which is dynamically filled with various entries:
<DataGrid
x:Name="searchResult"
AutoGenerateColumns="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=SearchResult}" />
Next to it is a bunch of TextBoxes, also dynamically generated:
<ItemsControl
x:Name="searchForm"
ItemTemplate="{StaticResource formInputTest}"
ItemsSource="{Binding SearchForm, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The template I use in the ItemsControl looks basically like this:
<TextBox
Tag="{Binding Index}"
Text="{Binding ElementName=searchResult, Path=SelectedValue.a, UpdateSourceTrigger=PropertyChanged}" />
Now lets asume my datagrid has 2 columns (a and b) and I have 2 TextBoxes with identical names (a and b).
Now I want to select a datagridrow and like the two text boxes to be automatically filled with the values from the corresponding row.
Unfortunately, I don't know how to get this dynamically Binding.
If I bind the TextBox's Text-property statically to searchResult->SelectedValue.a it works fine, but as you can see, only static.
Thats what I've done in the third code-box above.
The ItemsControl, which contains the TextBoxes, has it's binding to "SearchForm". SearchForm has the property "Index", which is the name of the Column.
So, the TextBox knows the name of the column it has to be binded to. To clarify this, I binded the TextBox.Tag to Index, which works fine.
Basically, I want my TextBox-Binding to look like this:
<TextBox
Text="{Binding ElementName=searchResult, Path=SelectedValue.{Binding Index}, UpdateSourceTrigger=PropertyChanged}" />
But, as you might know, a binding inside a binding does not work.
Does anybody know how to bind these to dynamically generated objects together?
Thanks for any hint!
UPDATE 2019/03/07
Thanks dymanoid!
This has got me some progress but I'm still not at the finish line.
I created the view-model property and binded both the Datagrid-SelectedItem and the TextBox-Text property to it.
Since the SelectedItem is a kind of a dictionary (BindablePublicDictionary) I use a converter (IMultiValueConverter) at the TextboxBinding, to convert the dictionary entry to both the index of the textbox and the text/value.
This is working fine, I select a datagrid-row and the 3 dynamically generated inputfields are beeing filled.
Unfortunatelly, if I edit one of these filled textboxes, the other 2 are emptied and my datagrid is unchanged.
I implemented OnPropertyChanged, my bindings are TwoWay and the ConvertBackFunction is called and returns the data in compliance with the targetType Type from the function itself.
Does anybody have experience with the bindings/converter between a datagrid/dictionary and textboxes, especially regarding the convertBack function?
I'm working on a digital signage WPF application and the basic structure is setup as follows:
I have a single view with a grid in it bound to a playlist object. The playlist contains panes, the panes contain playlist items, and the playlist items each contain a content item. I use DataTemplates to build the view for each piece, e.g.
<!-- This template represents a single content pane, no real display here, just empty space with a height and width -->
<DataTemplate DataType="{x:Type entities:ContentPane}">
<ContentControl
Content="{Binding CurrentPlaylistItem, Mode=OneWay}"
Height="{Binding Height, Mode=OneTime}"
Width="{Binding Width, Mode=OneTime}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
/>
</DataTemplate>
<!-- This is a playlist item which is contained within a ContentPane.
This also has no real display, just a placeholder for the content item, and will likely contain some transition information-->
<DataTemplate DataType="{x:Type entities:PlaylistItem}">
<inf:TransitionableContentControl Content="{Binding ContentItem}"></inf:TransitionableContentControl>
</DataTemplate>
<!-- image item template
the path can be any valid uri e.g. http://... or file://... -->
<DataTemplate DataType="{x:Type contentTypes:ImageContentType}">
<Grid Background="Transparent" x:Name="ImageType">
<Image Source="{Binding Bitmap, Mode=OneTime}"></Image>
<inf:BusinessAd
ContainerHeight="{Binding ImageHeight, Mode=OneTime}"
ContainerWidth="{Binding ImageWidth, Mode=OneTime}"
Visibility="{Binding AdText, Converter={StaticResource _VisibilityConverter}, Mode=OneWay}"
Text="{Binding AdText.Text, Mode=OneTime}"
AdFontSize="{Binding AdText.TextStyle.FontSize}"
AdFontFamily="{Binding AdText.TextStyle.FontFamily}">
<ContentControl
Content="{Binding AdText, Mode=OneTime}"
ContentTemplate="{StaticResource BusinessAdTextTemplate}">
</ContentControl>
</inf:BusinessAd>
</Grid>
</DataTemplate>
As the datacontext changes, the content in each pane transitions from one item to the next. I'm running into a problem where the user is placing the same item in a pane twice in a row, be it a video, image, etc. What happens is the change goes undetected and the UI does not update. In the case of a video, it freezes on the last frame of the first video and the whole application hangs.
I have tried doing an OnPropertyChanged("ContentItem") within the PlaylistItem class, tried setting Shared="False" on my data templates, I tried changing properties on the ContentItem object and raising PropertyChanged events, nothing seems to work. I turned on tracing on the databinding to see what was happening and it all appears to be working correctly. When I change properties on the ContentItem it shows a new hash for the new item, but no change on the UI.
Within the TransitionableContentControl in the PlaylistItem, the OnContentChanged override is never hit when going from one content item to the same one. If I swap that control out with a regular ContentControl, no change.
Former Employee is correct about the diagnosis. Furthermore, unless you manage to re-assign the inner datacontexts your templates will not restart from the beginning, as you have noticed. You will have to assign your CurrentPlaylistItem to some default empty item first to reset stuff all the way through. To avoid races and nasty flicker, do it on a dispatcher priority higher than UI rendering. Try this:
// CurrentPlaylistItem = pli ; -- not good
Application.Current.Dispatcher.Invoke (new Action (() =>
{
// clear item
// null doesn't work, because TransitionableContentControl's
// inner ContentControl would be empty and the brush created
// from it would be a null brush (no visual effect)
CurrentPlaylistItem = new PlaylistItem () ;
this.OnPropertyChanged ("CurrentPlaylistItem") ;
Application.Current.Dispatcher.Invoke (new Action (() =>
{
// set new item, possibly same as old item
// but WPF will have forgotten that because
// data bindings caused by the assignment above
// have already been queued at the same DataBind
// level and will execute before this assignment
CurrentPlaylistItem = pli ;
this.OnPropertyChanged ("CurrentPlaylistItem") ;
}), DispatcherPriority.DataBind) ;
})) ;
The issue here is the ContentControl (or rather the underlying DependencyProperty framework) doesn't fire OnContentChanged when .Equals is true for old and new Content. One workaround is to have your own dependency property and transition on CoerceValue. I'll email you the code and you can post it here.
When setting a contentcontrols template to xaml in code behind I cant access a static resource contained in the parent xaml.
I have a contentcontrol as follows:
<ContentControl x:Name="ccMaterial">
<ContentControl.Resources>
<x:Array x:Key="BondListKey" Type="sys:Int32"
xmlns:sys="clr-namespace:System;assembly=mscorlib" />
</ContentControl.Resources>
</ContentControl>
then in codebehind I am setting the template as follows:
string template = "<ControlTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
"<ComboBox Grid.Column=\"1\" Grid.Row=\"0\" ItemsSource=\"{Binding Source={StaticResource BondListKey}}\" />" +
"</ControlTemplate>";
ccMaterial.Template = (ControlTemplate)XamlReader.Parse(template);
The problem is that when i try to run this I get the exception saying that the resource "BondListKey" cannot be found. Can anyone explain why?
Please let me know if you need anymore information.
In response to Johns comments :
I have a tab item and I want to be able to display different controls within that tab based on a user selection somewhere else on the form. As an example if the user selected a car I would like to be able to change the control template to include a textbox for engine size, fuel type etc, if the user selected an orange I would like a control template that included variety and sweetness. I suspect I could get this functionality by drawing all possible controls on the tab, then altering the visible/enabled state of the relvant controls based on a datatrigger, but this would potentially involve a LOT of filtered controls ( as there may be many user selection types ). What I ideally want to be able to do is have the desired control template supplied as a string, parsed and assigned to the template of the control, thus modifying its contents at runtime.
Please let me know if that didnt make sense or you need anythign clarifying :)
StaticResource is a static lookup that is executed once at load time. If the target resource is not found at that time, you get an error, which is you're seeing now. Because you're loading the template in the context of the XamlReader the resources in your XAML aren't available. In most cases the fix is to use DynamicResource instead to provide a default value that gets updated when the resource becomes available, but Binding Source is not a DependencyProperty and so can't use Dynamic.
Rather than using a XamlReader, you can just declare your XAML in XAML and take advantage of the context that's available there:
<ContentControl x:Name="ccMaterial">
<ContentControl.Resources>
<x:Array x:Key="BondListKey" Type="sys:Int32"
xmlns:sys="clr-namespace:System;assembly=mscorlib" />
<ControlTemplate x:Key="MyTemplate">
<ComboBox Grid.Column="1" Grid.Row="0" ItemsSource="{Binding Source={StaticResource BondListKey}}" />
</ControlTemplate>
</ContentControl.Resources>
</ContentControl>
You can then still do the loading from code with:
ccMaterial.Template = ccMaterial.FindResource("MyTemplate") as ControlTemplate;
I'm using a ComboBox defined by:
<ComboBox Grid.Column="2" Height="29" HorizontalAlignment="Left" Margin="137,192,0,0" Name="componentsComboBox" VerticalAlignment="Top" Width="224"
IsEditable="True"
TextSearch.TextPath="Name">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
..to display a list of objects by their "Name" properties. I'm observing the following behaviors:
Click an item in the dropdown, and componentsComboBox.SelectedValue (and .SelectedItem) correspond to the clicked item. OK!
Start typing the name of an item, autocomplete fills in as you type, .SelectedValue (and .SelectedItem) correspond to the autocompleted item. GREAT!
Start typing the name of an item, autocomplete fills in as you type, hit delete to truncate to only what you have actually typed, .SelectedValue and .SelectedItem STILL correspond to the autocompleted item. NO! BAD WPF! BAD!
Similar behavior to 3 if you delete characters from the end of the textbox portion
In essence, if I have a List containing two objects defined by, e.g.,
{ new Component() { Name = "COMPONENT1"},
new Component() { Name = "COMPONENT2"} }
I want the values:
COMPONENT1
COMPONENT2
to appear in the drop-down portion, and if the user enters "COMP" I would like to recognize that they have entered a new value, but as it stands right the control makes it look like they selected COMPONENT1.
What am I missing here?
You can do this in WPF easily by setting the IsEditable and IsReadOnly properties to True & False respectively.
Ref.: http://msdn.microsoft.com/en-us/library/system.windows.controls.combobox.iseditable.aspx
e.g. How can I allow users to edit text in a ComboBox in WPF?
<Window x:Class="LearnWPF.EditableComboBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LearnWPF.EditableComboBox" Height="300" Width="300"
>
<Window.Resources>
<XmlDataProvider x:Key="items" XPath="//item">
<x:XData>
<items xmlns="">
<item>01</item>
<item>02</item>
<item>03</item>
</items>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<Grid>
<ComboBox IsEditable="True" DataContext="{StaticResource items}"
ItemsSource="{Binding}"/>
</Grid>
</Window>
I experienced this behavior today and agree that on the surface, it appears to be a bug. Digging a little deeper though reveals a bit of a gray area.
What I think is going on here is that the control is running a prefix matching algorithm every time the text box is updated, regardless of the keystrokes that cause the text box to be updated. So when you delete the auto-completed part of the text, the prefix matching algorithm still matches an item in the control's ItemsSource; the control isn't attempting to deduce that the end-user just deleted the auto-completed portion, so the algo shouldn't run.
Re: your comment to akjoshi's response, when you delete the "2", leaving just the "0" in the combo box's text box, the prefix matching algorithm correctly matches the "01" item.
The work-around I used was to only bind to the Text property of the ComboBox and build all view model logic off of that. SelectedItem, SelectedIndex, and SelectedValue are not bound at all.
After wasting hours on this, following on the heels of my Last Problem, I'm starting to feel that Framework 4 is a master of subtle evil, or my PC is haunted.
I have three comboboxes and a textbox on a WPF form, and I have an out-of-the-box Subsonic 3 ActiveRecord DAL.
When I load this "edit record" form, the comboboxes fill correctly, they select the correct items, and the textbox has the correct text. I can change the TextBox text and save the record just fine, but the comboboxes CANNOT BE CHANGED. The lists drop down and highlight, but when you click on an item, the item selected stays the same.
Here's my XAML:
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Asset</TextBlock>
<ComboBox Name="cboAsset" Width="180"
DisplayMemberPath="AssetName"
SelectedValuePath="AssetID"
SelectedValue="{Binding AssetID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Status</TextBlock>
<ComboBox Name="cboStatus" Width="180"
DisplayMemberPath="JobStatusDesc" SelectedValuePath="JobStatusID"
SelectedValue="{Binding JobStatusID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Category</TextBlock>
<ComboBox Name="cboCategories" Width="180"
DisplayMemberPath="CategoryName"
SelectedValuePath="JobCategoryID"
SelectedValue="{Binding JobCategoryID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Reason</TextBlock>
<TextBox Name="txtReason" Width="380" Text="{Binding Reason}"/>
</StackPanel>
Here are the relevant snips of my code (intJobID is passed in):
SvcMgrDAL.Job oJob;
IQueryable<SvcMgrDAL.JobCategory> oCategories = SvcMgrDAL.JobCategory.All().OrderBy(x => x.CategoryName);
IQueryable<SvcMgrDAL.Asset> oAssets = SvcMgrDAL.Asset.All().OrderBy(x => x.AssetName);
IQueryable<SvcMgrDAL.JobStatus> oStatus = SvcMgrDAL.JobStatus.All();
cboCategories.ItemsSource = oCategories;
cboStatus.ItemsSource = oStatus;
cboAsset.ItemsSource = oAssets;
this.JobID = intJobID;
oJob = SvcMgrDAL.Job.SingleOrDefault(x => x.JobID == intJobID);
this.DataContext = oJob;
Things I've tried:
Explicitly setting IsReadOnly="false" and IsSynchronizedWithCurrentItem="True"
Changing the combobox ItemSources from IQueryables to Lists.
Building my own Job object (plain vanilla entity class using INotifyPropertyChanged).
Every binding mode for the comboboxes.
ItemsSource="{Binding}"
The Subsonic DAL doesn't implement INotifyPropertyChanged, but I don't see as it'd need to for simple binding like this. I just want to be able to pick something from the dropdown and save it.
Comparing it with my last problem (link at the top of this message), I seem to have something really wierd with data sources going on. Maybe it's a Subsonic thing?
EDIT: For some reason the set accessor is hit only on the AssetID property and only the first time. WPF is now heading for WTF :)
EDIT 2: You gotta be kidding me- I've removed the binding (ie it only has a displaymemberpath, a valuememberpath and an itemssouce) and it's STILL doing it! It accepts your first selection, and then won't change.
WPF Combo Boxes will not change the selected item if the currently selected item and the item that was just selected are considered equal by the object.Equals() method called on the newly selected object (i.e newlyslected.Equals(previoslySelected) ).
Overriding the Equals method on the class your binding the combobox items, should resolve the issue your are seeing.
I've narrowed it down to the Subsonic objects used as ComboBoxItems.
If you create a new class that uses exactly the same code as the relevant parts of the Subsonic one, it works.
If you use POCOs/datatables for the combos and Subsonic for the record being edited, it works.
But if you use Subsonic for both, it doesn't.
I had hoped to extend the subsonic objects and not have to code a full-blown BLL tier. Looks like I'm faced with doing that or throwing out Subsonic for the DAL. I might post a more specific question for the Subsonic folks.
Many thanks to all who contributed.
Old topic but I had the same problem and difficulty finding solution. This might help someone else.
Clue is above in WPF not detecting a different item has been seleted by user. (Symptom - event ComboBox_SelectionChanged only fires on first selection)
My scenario - lookup combo populated from IList built from a DISTINCT query. In this case the result of using NHibernate ICriteria.SetResultTransformer which only returns SOME fields, importantly NOT including the unique entity ID.
Solution - loop thru' IList after retrieval and give each entity a unique ID. WPF sees them as individuals and behaves appropriately.
Its only a value lookup - its the value content I was after.
The 'temporary' entities are never persisted. In this case it was a better approach than messing with overriding the object's Equals method for the sake of a simple GUI issue. An alternative would be to just copy or tranform the list into a format where WPF uses the value field to determine 'difference'...
Sounds like the field is somehow readonly, or that your change isn't being persisted. After the binding sets the new value, it will re-read the property to ensure that it was actually changed. If your property returns the old value, then it'll be re-selected in the combo box, giving the appearance that the value never changed.
I don't know that DAL, but can you step through the property setter code? You might also have an issue with type conversion.
EDIT reading your comment about the red rectangle -- it sounds as though your property (or something to do with the binding) is raising an exception. Unless, of course, you're using data validation in your UI. You might turn 'Break on all exceptions' in the debugger's settings, assuming you're using Visual Studio.
EDIT 2 You should check the VS Output pane for any error messages related to binding. You can also read this blog post which gives more info on debugging bindings.
It's hard to tell from a small sample of your code but try commenting out the line:
//this.DataContext = oJob;
and see if this helps.
Setting the DataContext and ItemsSource might be causing a conflict.
Did you write any global style for your combo box which may have a bug or something missing? Or are you using pure default styles for your combobox? Try removing any default styles applied.
Are you wiring up any events? If your code hooks up for event like PreviewMouseLeftButtonUp and marks event as handled then probably combobox may ignore and wont select anything.