WPF ComboBox initially displays first item at wrong size - wpf

I have a ComboBox on a WPF window that is giving me some heartache, in that when first displayed, the first selection is rendered improperly. The ComboBox does not display text only; it displays an object of a type that descends from UserControl. Here's the XAML for the ComboBox itself:
<ComboBox Grid.Column="0"
Height="69"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
ItemsSource="{Binding Path=ViewChoices,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Mode=OneWay}"
Margin="10"
Name="ViewPicker"
SelectionChanged="ViewPicker_SelectionChanged"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center" />
As you can see, the ComboBox's ItemsSource is bound to a property of the UserControl that owns it called ViewChoices. The ViewChoices object is an ObservableCollection.
The contents of the ComboBox is set in code in the code behind, as the exact contents in the final code will be read from an XML file; the values are hard coded right now. Essentially, a CameraViewChoice object is created for each XML row read and added to the ViewChoices collection. This all happens in the UserControl's default constructor, after called InitializeComponent(). In the UserControl's Loaded event handler, I have code which sets the ComboBox's SelectedIndex property to 0.
The CameraViewChoice object is descended from UserControl. Here's the Xaml for this control:
<UserControl x:Class="CarSystem.CustomControls.CameraViewChoice"
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:cs="clr-namespace:CarSystem.CustomControls"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="50">
<Border BorderBrush="Black" BorderThickness="1">
<Grid>
<Image Opacity="0.35" Source="..." Stretch="Uniform" />
<Label Content="{Binding Path=Text}"
FontSize="18"
FontWeight="Bold"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Border>
</UserControl>
Here's the code-behind for the CameraViewChoice object:
public partial class CameraViewChoice : UserControl {
public static readonly DependencyProperty AttachedCameraProperty = DependencyProperty.Register( "AttachedCamera", typeof( Camera ), typeof( CameraViewChoice ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsRender ) );
public static readonly DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof( string ), typeof( CameraViewChoice ), new FrameworkPropertyMetadata( string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsRender ) );
public Camera AttachedCamera {
get { return (Camera) GetValue( AttachedCameraProperty ); }
set { SetValue( AttachedCameraProperty, value ); }
}
public string Text {
get { return (string) GetValue( TextProperty ); }
set { SetValue( TextProperty, value ); }
}
public CameraViewChoice() {
InitializeComponent();
}
This is all working fine but there's one problem. When the program starts running and the ComboBox displayed for the first time, the selected item is displayed all wrong. The label is blank and the CameraViewChoice control is displayed too large, so much so that the bottom of it is cut off. What I'm seeing is a blank CameraViewChoice object displayed without scaling to the ComboBox.
If I choose an item in the list, all of the choices in the list display properly and are sized properly & look fine after that, including the selected one. The problem is only when the window is first displayed.
Does anyone have any ideas about what's going on?
Tony
Edit:
I did some research on Google & MSDN Magazine and I found an article by Josh Smith about Data Templates. From there, I made the following changes to the XAML for my ComboBox:
<ComboBox Grid.Column="0"
Height="69"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
ItemsSource="{Binding Path=ViewChoices,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Mode=OneWay}"
Margin="10"
Name="ViewPicker"
SelectionChanged="ViewPicker_SelectionChanged"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<cs:CameraViewChoice Margin="10" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This is better as the items in the ComboBox do not change size, but the initial display is still too large. That is, it's getting cut off at the bottom. Further, the size of the selected item is consistently too large. So when you select one item in the list, it's displayed in the combobox partially clipped.
I want the choice displayed in the ComboBox with all of it fitting in side of it. Any suggestions for changes to the CombobBox's ItemTemplate?

In the Loaded event, provided you have at least 2 items, set the SelectedIndex to 1. After this, no matter how many items you have, call InvalidateMeasure and UpdateLayout on the ComboBox, then set the SelectedIndex to 0.

Here's what I think is happening.
You are using a standard ComboBox and dynamically adding UIElements to it. When the ComboBox is first displayed, there are no items, so it uses a default template. After you start adding UIElements to it, the renderer then performs it measuring and arranging. In essence, it's only learning what it should look like after the UIElements are created and inserted (but it still needed to know what to look like before that happened).
My suggestion would be to move from this development pattern to a more common methodology. Instead of creating UIElements on the fly, just create an ObservableCollection of CameraChoices (or whatever name would be appropriate). Typically this would be contained in a ViewModel.
Then instead of creating a UserControl and inserting it into the ItemsSource of the ComboBox, you'd be better served to create an ItemsTemplate (where you can use the UserControl) for the ComboBox. Alternatively, you can use a DataTemplate of the same type as the object in the ObservableCollection.
This will provide a more robust mechanism for displaying the list of items and provide you with a way to get to the raw data instead of having to deal with a UIElement when the SelectionChanged event is signaled.

Related

Binding datacontext of usercontrol to mainwindow

I have a UserControl positioned inside of the MainWindow. The UserControl runs a query and populates certain TextBlocks within it. I also want to populate TextBlock in the MainWindow from the same returned data.
How do I bind the MainWindow data to the UserControl? I have tried this:
<MainWindow DataContext="{Binding Path=DataContext, ElementName=UserControlName}">
Any help would be appreciated. Thanks!
Here is a simple working example.
The UserControl XAML contains just a two-way bound text box. The relative source stuff is more verbose than you need, you could have a data context set above that, but it's just to make it clear where the property is coming from:
<TextBox x:Name="ucTextBox"
Text="{Binding Path=UcText,
RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
User Control code behind declares either a dependency property or, as shown here, a regular property implementing INotifyPropertyChanged:
private string _ucText;
public string UcText
{
get { return _ucText; }
set
{
_ucText = value;
OnPropertyChanged("UcText");
}
}
The MainWindow XAML then sets it's own text block to the text property from the textbox in the user control, like so:
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=UcText, ElementName=uc1}"/>
<local:UserControl1 x:Name="uc1" />
</StackPanel>
Nothing extra is required in the MainWindow codebehind.
What this results in is a text box (in the user control) which - as you type inside it - updates the text block on the main window.

Bind user control dependency properties in MVVM style Windows Phone app

I'm having some issues with binding some custom controls in a Windows Phone app right now. Usually this is never an issue but apparently my mind can't comprehend this today.
So I'm doing an MVVM style setup which is good. I have my page with a view and also a viewmodel. Now on a WebClient callback I assign the dataContext of my view to the list of models in my ViewModel, nice and simple thus far...now in my view I created a ListBox with a custom control in the datatemplate which is basically a cell in the list. I once again set my user controls dataContext to binding, and binding all the models values to the regular UI elements works no problem.
Here's a sample:
<Grid Grid.Column="0">
<Image Source="{Binding SmallPath}" VerticalAlignment="Top"/>
</Grid>
<Grid Grid.Column="1">
<StackPanel Margin="12,0,0,0">
<TextBlock x:Name="MemberId_TextBlock" Text="{Binding MemberId}" FontSize="28"
Margin="0,-8,0,0"
Foreground="{StaticResource PhoneForegroundBrush}"/>
<StackPanel Orientation="Horizontal" Margin="0,-11,0,0">
<TextBlock Text="{Binding DaysReported}" FontSize="42"
Margin="0,0,0,0"
Foreground="{StaticResource PhoneAccentBrush}"/>
<TextBlock Text="days" FontSize="24"
Margin="3,19,0,0"
Foreground="{StaticResource PhoneSubtleBrush}"/>
</StackPanel>
</StackPanel>
</Grid>
That's in my user control, and here's the the view where the usercontrol is housed:
<Grid x:Name="LayoutRoot" Background="Transparent">
<ListBox Name="TopSpotter_ListBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<!--<TextBlock Text="{Binding MemberId}"/>-->
<controls:TopSpotterItemControl DataContext="{Binding}"/>
<Grid Height="18"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Now this is good enough but what I want to do in my view is set data from my model like Booleans that determine whether or not I should show certain Grids etc. So if I try to set a dependency property explicitly in my control it fires and will run logic in the Getter/Setters for instance. HOWEVER if I try to set these custom objects from a binding source it won't actually set.
Here's what works:
<controls:TopSpotterItemControl ChampVisibility="True">
This way will trigger the ChampVisibility property and then in the code behind of the user control I can set visibilities.
Here's what fails but I want to work:
<controls:TopSpotterItemControl ChampVisibility="{Binding IsChamp">
In addition I can still set the DataContext to {Binding} and the result will be unchanged.
In this scenario IsChamp is part of my model that I would like to bind to this user control which I guess comes from the dataContext being set on the view from the viewModel. I'm not sure what I can do to get this so the bindings work etc. without having to set custom properties.
Finally, here's my user control:
public partial class TopSpotterItemControl : UserControl
{
public string MemberId
{
get
{
return this.MemberId_TextBlock.Text;
}
set
{
this.MemberId_TextBlock.Text = value;
}
}
public bool ChampVisibility {
set
{
if (value)
{
this.Champ_Grid.Visibility = System.Windows.Visibility.Visible;
}
}
}
public static readonly DependencyProperty MemberNameProperty =
DependencyProperty.Register("MemberId", typeof(string), typeof(TopSpotterItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty ChampVisibilityProperty =
DependencyProperty.Register("ChampVisibility", typeof(bool), typeof(TopSpotterItemControl), new PropertyMetadata(null));
public TopSpotterItemControl()
{
InitializeComponent();
}
}
Bit long winded and I hope I made things on the issue clear. My one major hang up so far, and I'd like to abstract as much control as I can to the user control via dependency properties explicitly set in xaml, rather than setting up binding in its xaml that depend on the knowledge of a model. Thanks!
Your DependencyProperty is badly formed. (I also don't see Champ_Grid defined in your class or XAML, but I assume that is an ommission)
Setting ChampVisibility = true in code works because it is unrelated to the DependencyProperty.
You can tell easily because the default value for your DP is invalid. It will compile, but the instance constructor will through an exception if it is ever invoked.
new PropertyMetadata(null)
bool = null = exception
If you call GetValue(TopSpotterItemControl.ChampVisibilityProperty) from somewhere you can confirm all of the above.
You should make changes to instance fields in the property changed handler and declare the property like the following, it will work:
Note that the property has to change (not just be set) for the event to be raised.
public bool ChampVisibility
{
get { return (bool)GetValue(ChampVisibilityProperty); }
set { SetValue(ChampVisibilityProperty, value); }
}
public static readonly DependencyProperty ChampVisibilityProperty =
DependencyProperty.Register("ChampVisibility ", typeof(bool), typeof(TopSpotterItemControl), new PropertyMetadata(true, (s, e) =>
{
TopSpotterItemControl instance = s as TopSpotterItemControl;
instance.Champ_Grid.Visibility = instance.ChampVisibility ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
}));
Incidentally, your MemberId DependencyProperty is also completely wrong and cannot work.
Note:
The Binding on your TextBox works, because it is binding to the DataContext (your model), so it probably shows the right value.
The Dependency property in your UserControl will never be set though.
Use the propdp code-snippet in Visual Studio so you dont have to concern yourself with the complexities of Dependency Property declaration.
Also check this out for more info about Dependency Properties

Binding Telerik RadRichTextBox through MVVM

I can't seem to pick my way through Telerik's terrible documentation for binding through an MVVM situation. We've got the standard view, and view model. The viewmodel exposes a property, 'Body' that is supposed to represent what the user is typing. Here are the relevant lines of xaml:
<telerik:DocxDataProvider
x:Name="DocxProvider"
RichTextBox="{Binding ElementName=editor}"
Docx="{Binding Body, Mode=TwoWay,
UpdateSourceTrigger=LostFocus}" />
<telerik:RadRichTextBox
Grid.Row="1"
x:Name="editor"
Margin="0"
AllowDrop="True"
ShowComments="False"
MinWidth="800"
MinHeight="300"
MaxWidth="1024"
MaxHeight="1200"
HorizontalAlignment="Left"
HorizontalContentAlignment="Left"
Width="790"
Padding="20,20,0,20"
TabIndex="10"
Cursor="IBeam"
IsSpellCheckingEnabled="True" >
</telerik:RadRichTextBox>
But when I set a break point on Body, it never gets hit. What property do I need to bind to so that my view model actually gets the content of the text box?
Have you tried binding to Rtf property of telerik:RadRichTextBox; another thing can be the ordering of your elements, can you try declaring <telerik:RadRichTextBox before <telerik:DocxDataProvider
Also have a look at this thread on telerik forum -
http://www.telerik.com/community/forums/wpf/richtextbox/binding-document.aspx
Did you remember to set the datacontext in the constructor of the xaml.cs file:
public View(ViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
This is how you hook up the viewmodel to the view.

WPF - UI Not Updating When Item Added to Bound Collection

I have a Generic List of objects that I've bound to a custom control. Everything seems to work OK in the code-behind, but any changes I make to the collection don't seem to reflect in the UI (even though they're working find in all the code behind).
Here's the XAML for my UI:
<controls:ControllableListView x:Name="lvSummaryCaseEvents" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3" Label="Case Events:" ItemsSource="{Binding CaseEvents}" AddButtonClicked="ControllableListView_AddButtonClicked">
And the code behind where I add the item into the collection:
_caseScreen.CaseEvents.Add(caseEvent);
//after the line above executes, lvSummaryCaseEvents (in the debugger) shows the correct number of items and the items' values are all correct. No change to the UI whatsoever
The ItemSource property in my user control:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(System.Collections.IList), typeof(ControllableListView));
public System.Collections.IList ItemsSource
{
get
{
return this.GetValue(ItemsSourceProperty) as System.Collections.IList;
}
set
{
this.SetValue(ItemsSourceProperty, value);
}
}
And finally, the XAML for a standard ListView that lives in my user control which is bound to the ItemsSource property listed above:
<ListView Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" Name="lvListView" ItemsSource="{Binding ItemsSource}" View="{Binding View}" SelectionChanged="lvListView_SelectionChanged" />
Just to reiterate, everything works fine when I display the collection the first time around, but when I add an item to the collection, the UI does not reflect the changes made.
Thanks in advance,
Sonny
Change your collection to ObservableCollection<T>

Displaying FontFamily in Combobox

My goal is to manipulate the text-styles of my application via DependencyProperties. I got a diagram in which the texts are to be manipulated in size, fontfamily, color, etc. So I'd like to use an interface similar to a rich text editor like Word.
I'm using this code in my TextStyleVM http://shevaspace.blogspot.com/2006/12/i-have-some-fun-with-formattedtext_14.html
So I have a FontFamilyProperty and a Getter and Setter for it:
public static DependencyProperty FontFamilyProperty =
DependencyProperty.Register(
"FontFamily",
typeof(FontFamily),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
SystemFonts.MessageFontFamily,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(IsValidFontFamily));
public FontFamily FontFamily
{
get { return (FontFamily)base.GetValue(FontFamilyProperty); }
set { base.SetValue(FontFamilyProperty, value); }
}
Then there is a ToStyle method, which sets the style for the labels of the diagram, which are to be manipulated:
Style style = new Style();
Binding fontFamilyBinding = new Binding("FontFamily");
fontFamilyBinding.Source = this;
Setter fontFamilySetter = new Setter();
fontFamilySetter.Property = TextBlock.FontFamilyProperty;
fontFamilySetter.Value = fontFamilyBinding;
style.Setters.Add(fontFamilySetter);
return style;
Now this works for a TextBox. The textbox displays the current FontFamily, and if I enter a new, valid FontFamily like Arial into the textbox the FontFamily of the labels are changed.
However, what I'd like to have is a combobox, which displays the SystemFonts and where I can choose one FontFamily for my labels. However, the binding doesn't seem to work. Neither the system fonts nor the current fonts of the labels are displayed. The combobox is just empty.
This is my xaml:
<r:RibbonLabel Content="FontFamily" />
<!--these do not work-->
<r:RibbonComboBox SelectedItem="{Binding FontFamily}"/>
<r:RibbonComboBox ItemsSource="{Binding FontFamily}"/>
<!--this works-->
<r:RibbonTextBox Text="{Binding FontFamily}"/>
Now, I assume I have to set a different Setter for a ComboBox in the ToStyle Method. But I have no clue, which one. Maybe someting like this:
fontFamilySetter.Property = ComboBox.ItemSource;
However, if I set that Property, the TextBox still works. So is this the wrong place to start at? I'd also be grateful if someone could hint me to some documentation about using these Style-, Setter-, Binding-key-words, which are used in the ToStyle method, since this is somebody elses code I'm working with.
ItemsSource here expects a collection; e.g. Fonts.SystemFontFamilies
<ComboBox ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}"/>
Actually, here's a very nice link covering font selection:
http://www.hanselman.com/blog/LearningWPFWithBabySmashCustomerFeedbackAndAWPFFontComboBox.aspx
Scott Hanselman even shows how to render each font item in combobox with it's own font family.
Added per OP comment.
Here's an example of binding to dependency property. Property is named "MyFontFamily" to avoid collision with existing Window's property. Sorry, no Ribbon controls (I have bare 3.5 sp1).
Window1.xaml
<Window
x:Class="SimpleWpf.Window1"
x:Name="ThisWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical">
<ComboBox
SelectedItem="{Binding MyFontFamily, ElementName=ThisWindow}"
ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}"/>
<TextBlock
Text="Lazy dog jumper"
FontFamily="{Binding MyFontFamily, ElementName=ThisWindow}"
FontSize="24"/>
</StackPanel>
</Window>
Window1.xaml.cs
public partial class Window1 : Window
{
// ...
public static readonly DependencyProperty MyFontFamilyProperty =
DependencyProperty.Register("MyFontFamily",
typeof(FontFamily), typeof(Window1), new UIPropertyMetadata(null));
}
A just in Xaml solution with fonts sorted in alphabetical order:
<Window x:Class="WpfFontsComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Height="350" Width="525">
<Window.Resources>
<CollectionViewSource x:Key="SortedFontsCollection" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}" >
<CollectionViewSource.SortDescriptions>
<ComponentModel:SortDescription PropertyName="Source" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<StackPanel>
<Label Content="42" FontFamily="{Binding ElementName=comboBoxFonts, Path=SelectedItem}" />
<ComboBox x:Name="comboBoxFonts" ItemsSource="{Binding Source={StaticResource SortedFontsCollection}}" />
</StackPanel>
</Window>
A great Font Combobox for WPF can be found here:
CodeProject.com: A XAML-Only Font ComboBox
It is pure XAML, can be just copied/pasted and even sorts the fonts properly. The article also describes nicely all the gotchas encountered and how to solve them.

Resources