How to Make Microsoft.VisualStudio.Diagnostics.UI.Controls.MultiSelectComboBox Work - c#

What I Try to Achieve:
I'm developing a Visual Studio plugin, and I need a MultiSelectComboBox. I want to fit in with the look and feel of VisualStudio so it seemed a good idea to use their own class for this:
public class MultiSelectComboBox : UserControl, IComponentConnector, IStyleConnector
Name: Microsoft.VisualStudio.Diagnostics.UI.Controls.MultiSelectComboBox
Assembly: Microsoft.VisualStudio.Diagnostics.Common, Version=12.0.0.0
Microsoft uses this class in their Code Analysis page: View/Other Windows/Code Analysis.
The Problem:
Of course it just doesn't work right when I want to use it. :)
Here's a sample code how I used it:
public TestClass()
{
InitializeComponent();
multiSelectComboBox.ItemsSource = new string[] { "Item 1", "Item 2", "Item 3" };
multiSelectComboBox.AllItemsText = "All items";
}
And here's the XAML markup:
<UserControl ...
xmlns:vsUiControls="clr-namespace:Microsoft.VisualStudio.Diagnostics.UI.Controls;assembly=Microsoft.VisualStudio.Diagnostics.Common"
...>
<vsUiControls:MultiSelectComboBox x:Name="multiSelectComboBox"/>
</UserControl>
Now the MultiSelectComboBox appear and you can interact with it, however, when you select some items, but not all, the items should be displayed like this: Item 1; Item 3 (assuming you selected all but Item 2). However, the displayed text is just Item 1Item 3, totally missing the separator.
The funny thing (that I have overlooked for quite a while) is that if you debug your code and ask for multiSelectComboBox.SelectedItemsText it returns the right values separated with the semicolon.
So the question is, if the value is stored right, why is it not displayed correctly when I use it in my code, but right when used by Microsoft on the Code Analysis page?
The XAML markup describing the style of MultiSelectComboBox contains only one instance of SelectedItemsText, which is a binding. Please see what I've got from .Net Reflector below:
<local:MultiSelectComboBox
p1:UserControl.Name="_this"
p1:AutomationProperties.Name="{Binding RelativeSource={RelativeSource Self},Path=AllItemsText}"
xmlns:p1="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:Microsoft.VisualStudio.Diagnostics.UI.Controls;assembly=Microsoft.VisualStudio.Diagnostics.Common,Version=12.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a">
...
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<local:CheckComboBox
CheckComboBox.Name="_comboBox"
p4:FrameworkElement.Style="{StaticResource ComboStyle}"
p4:Control.HorizontalContentAlignment="Stretch"
p4:KeyboardNavigation.DirectionalNavigation="Continue"
p4:AutomationProperties.Name="{Binding ElementName=_this,Path=SelectedItemsText,Mode=OneWay}"
xmlns:p4="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ItemsControl.ItemTemplate>
...
</ItemsControl.ItemTemplate>
</local:CheckComboBox>
</Grid>
</local:MultiSelectComboBox>
I am not sure why SelectedItemsText is bound to the AutomationProperties.Name (attached?) property, but this is what .Net Reflector gave me. If I debug my code, I can find the semicolon separated values stored in the Name property of CheckedComboxBox control within the MultiSelectComboBox.
The values seem to be stored right, binding seems to work, yet the text displayed to the UI does not contain the separator. I'm just puzzled...

I took a look at decompiled sources and it seems that property SelectedItemsText is used only for UI automation (attached property AutomationProperties.Name). Real display text is showind with this XAML:
<TextBlock Name="PART_SummaryPartialSelection" Grid.Row="0" Style="{StaticResource DropDownTextBlockStyle}" Visibility="{Binding Path=AllItemsSelected, ElementName=_this, Converter={StaticResource booleanToVisibilityConverterNegative}}">
<ItemsControl Name="PART_Items" Focusable="False" Background="#00FFFFFF" IsHitTestVisible="False" x:Uid="M113" ItemsSource="{Binding SelectedItems, ElementName=_this}" ItemTemplate="{Binding DisplayAreaTemplate, ElementName=_this}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate x:Uid="M115">
<StackPanel IsItemsHost="True" Orientation="Horizontal" x:Uid="M116" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</TextBlock>
So it is just horizontal StackPanel with items one by one without any separator. So you will need to modify this template or just add semicolon at your items (looks like Visual Studio does it this way as it shows semicolon event after last item - item1; item2;)

Related

How do you add a button to a TreeView using a HierarchicalDataTemplate?

I've got a TreeView that is constructed like this:
//This is for dynamically building a treeview with templates from an XML file
XmlTextReader xmlReader1 = new XmlTextReader("HierarchicalDataTemplate1.xml");
HierarchicalDataTemplate hierarchicalDataTemplate1 = XamlReader.Load(xmlReader1) as HierarchicalDataTemplate;
And it reads an XML file like this:
<HierarchicalDataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" ItemsSource="{Binding XPath=SubCategory}">
<TextBlock FontSize="36" FontFamily="K22 Monastic" Text="{Binding XPath=#CategoryName}" />
<Button>Add Subordinate Unit</Button>
</HierarchicalDataTemplate>
But it throws a runtime error on adding the button:
''Template' property has already been set on 'HierarchicalDataTemplate'.' Line number '3' and line position '4'.
Is what I'm trying to do possible? If I take out the script for adding a button everything works fine.
Thanks!
One obvious error is that you've got two elements at the root level of the template's visual tree. You can't do that. A DataTemplate or HierarchicalDataTemplate can have only one child. So your first step is to make that one child a control that supports multiple children of its own, then put your TextBlock and your Button in that. StackPanel is a good one:
<HierarchicalDataTemplate
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
ItemsSource="{Binding XPath=SubCategory}"
>
<StackPanel Orientation="Horizontal">
<TextBlock
FontSize="36"
FontFamily="K22 Monastic"
Text="{Binding XPath=#CategoryName}"
/>
<Button>Add Subordinate Unit</Button>
</StackPanel>
</HierarchicalDataTemplate>
It's interesting to note that when I paste your template XAML into the XAML designer, I get a different error: "The property 'VisualTree' is set more than once" -- but when I duplicate your XamlReader.Load(), code, I get the same exception and message as you (and the same fix corrects it).
Google turns up zero results for "Template property has already been set on HierarchicalDataTemplate". Well, maybe it'll have one now.

ContentPresenter in ItemControl.ItemTemplate to show displaymemberpath of itemscontrol

I want to know is there anyway to put contentpresenter in itemtemplate of an itemscontrol to display my data. I don't want hard code binding like Text="{Binding username}" cause I am building a custom control, I think ContentPresenter is what I want. But after I tried using contentpresenter, it give me stackoverflowexception.
<ItemsControl ItemsSource="{Binding SelectedItems, ElementName=listbox}" DisplayMemberPath={Binding DisplayMemberPath}">
<ItemsControl.ItemPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Separator" Text=", "/>
<ContentPresenter/>
<!--<TextBlock Text="{Binding username}"/>-->
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
That's my code.
If without those seperator and itemtemplate, I able to display my data by just using the displaymemberpath, but it stack all the name together. I still finding any solution to solve it. I hope you can provide some ideas to do this.
The answer is no, you can't. A ContentPresenter is supposed to be used in a ControlTemplate, not a DataTemplate, so it is not the right control to use. From the linked page on MSDN:
You typically use the ContentPresenter in the ControlTemplate of a ContentControl to specify where the content is to be added.
What you can do alternatively, is to declare a number of DataTemplates in a Resources section (complete with Binding Paths) for different types of data and omit the x:Key directives, eg. do not name them. Also, do not specify one for the ItemsControl.ItemTemplate.
When doing this, WPF will implicitly select the correct DataTemplate for the relevant data type and so you can have different outputs for different data types. See the The DataType Property section of the Data Templating Overview page on MSDN for further explanation of this technique.
Yes, and it works well. Outside of a ContentControl's template, you must bind the Content by hand:
<ContentPresenter Content="{Binding username}"/>
I do this a great deal and it never misbehaves. ContentPresenter seems to be implemented for general use. I wonder if the API docs overstate its relationship to ContentControl.
I found an easier way to solve this problem by using horizontal listbox. Thanks for responses

Basic ListView Styling

I have read the docs and tried many samples but to be honest the samples look like a big jumbled mess and don't seem to make much sense.
Can anyone recommend any easy to follow tutorials or docs on how to style a ListView control in XAML? (Without Expression Blend)
The two main common tricks to styling a ListView are to style the items and change the kind of container the listbox uses to lay the items out.
Styling an Item
This basically means setting the ItemTemplate in xaml to something that knows how to dispay the thing that is the content of the listbox's ItemsSource, typically using bindings.
For example, if you have an ObservableCollection<Customer> bound to the listbox where customer is defined as:
public class Order
{
public int Id { get; set; }
public string OrderReference { get; set; }
public string CustomerName { get; set; }
}
Then you might style the items with a data template as follows:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding CustomerName}" />
<TextBlock Text="{Binding OrderReference}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A basic example but you get the idea.
Changing how items are laid out
Essentially you might not want the default behaviour where items are listed vertically, so you can change the container control used inside the listbox to something more suitable using the ItemsPanel property. If, for example you had an item template that looked like an item from the "large icons" view in Windows Explorer, then you might want the listbox to use a WrapPanel rather than a StackPanel (I'm pretty sure it's a StackPanel):
<ListBox>
<ListBox.ItemsPanel>
<DataTemplate>
<WrapPanel>
<ContentPresenter />
</WrapPanel>
</DataTemplate>
</ListBox.ItemsPanel>
</ListBox>
Again a basic example.
I wrote all this code from memory into StackOverflow so apologies if there are a few typos or mis-remembered bits in there.
HTH.
Check out this 2 part "WPF ListView Styling Tutorial" article: http://blog.vascooliveira.com/wpf-listview-styling-tutorial-part-i/
Note: on my computer, the XML (XAML) code does not display correctly, the < and > signs appear as "&lt" and "&gt".

Dragging item from DataGridDragDropTarget to Label

I'm using the ListBoxDragDropTarget from the Silverlight Toolkit (April 2010) with SL 4.
I want to drag items from the list box onto a Label and handle the drop event there.
However it seems a bit complicated. The regular Drop event of the Label never gets fired. I suppose that is because the Silverlight Toolkit has its own way of handling Drag & Drop which is only partially compatible.
Looking arround I found the Microsoft.Windows.DragDrop.DropEvent and attached a handler to this event. And it worked!! I got the Drop event. However I'm not sure how to get to the real object that was dragged (a string).
I tried e.Data.GetData(typeof(string)) but I got nothing. Looking at the available formats there is a System.Windows.Controls.ItemDragEventArgs object. Inside this I found an array of System.Collections.ObjectModel.Selection which then has an Item property. I suppose in this Item property I find my object, but the whole method seems a bit fragile and I'm not convinced that is the official way to do this.
Is there any better way?
U can also use another ListBox
For ex: include namespace
xmlns:toolKit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
Let us add the “ListBoxDragDropTarget inside the Grid. Set the attribute “AllowDrop” to True. Once it is set to true, it will be able to catch the drop event inside the control.
Now we will add a ListBox inside the ListBoxDragDropTarget and set properties whichever u like. suppose
<toolKit:ListBoxDragDropTarget AllowDrop="True">
<ListBox x:Name="customerListBoxMain" Height="200" Width="200"
DisplayMemberPath="Name">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</toolKit:ListBoxDragDropTarget>
And add another ListBox
<toolKit:ListBoxDragDropTarget AllowDrop="True">
<ListBox Height="200" Width="200" DisplayMemberPath="Name">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</toolKit:ListBoxDragDropTarget>
Now to fetch some data and set it to the Source of the first ListBox from code behind. Here is the sample code:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
customerListBoxMain.ItemsSource = PersonDataProvider.GetData();
}
}
It's Done..

How can I bind the nested viewmodels to properties of a control

I used Microsoft's Chart Control of the WPF toolkit to write my own chart control.
I blogged about it here. My Chart control stacks the yaxes in the chart on top of each other. As you can read in the article this all works quite well. Now I want to create a viewmodel that controls the data and axes in the chart. So far I'm able to add axes to the chart and show them in the chart. But I have a problem when I try to add the lineseries because it has one DependentAxis and one InDependentAxis property. I don't know how to assign the proper xAxis and yAxis controls to it.
Below you see part of the LineSeriesViewModel. It has a nested XAxisViewModel and YAxisViewModel property.
public class LineSeriesViewModel : ViewModelBase, IChartComponent
{
XAxisViewModel _xAxis;
public XAxisViewModel XAxis
{
get { return _xAxis; }
set
{
_xAxis = value;
RaisePropertyChanged(() => XAxis);
}
}
//The YAxis Property look the same
}
The viewmodels all have their own datatemplate.
The xaml code looks like this:
<UserControl.Resources>
<DataTemplate x:Key="xAxisTemplate" DataType="{x:Type l:YAxisViewModel}">
<chart:LinearAxis x:Name="yAxis" Orientation="Y" Location="Left" Minimum="0" Maximum="10" IsHitTestVisible="False" Width="50" />
</DataTemplate>
<DataTemplate x:Key="yAxisTemplate" DataType="{x:Type l:XAxisViewModel}">
<chart:LinearAxis x:Name="xAxis" Orientation="X" Location="Bottom" Minimum="0" Maximum="100" IsHitTestVisible="False" Height="50" />
</DataTemplate>
<DataTemplate DataType="{x:Type l:LineSeriesViewModel}">
<!--Binding doesn't work on the Dependent and IndependentAxis! -->
<!--YAxis XAxis and Series are properties of the LineSeriesViewModel -->
<l:FastLineSeries DependentAxis="{Binding Path=YAxis}"
IndependentAxis="{Binding Path=XAxis}"
ItemsSource="{Binding Path=Series}"/>
</DataTemplate>
<Style TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<!--My stacked chart control -->
<l:StackedPanel x:Name="stackedPanel" Width="Auto" Height="Auto" Background="LightBlue">
</l:StackedPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ClipToBounds="True">
<!-- View is an ObservableCollection of all axes and series-->
<ItemsControl x:Name="chartItems" ItemsSource="{Binding Path=View}" Focusable="False">
</ItemsControl>
</Grid>
This code works quite well. When I add axes they get drawn. But the DependentAxis and InDependentAxis of the lineseries control stay null, so the series doesn't get drawn. How can I bind the nested viewmodels to the properties of a control?
It should work. A few things you can check:
Does the Series Binding work? If so, try to figure out what's the difference.
Are you sure that the XAxis and YAxis properties actually have values? Try putting a breakpoint in the getter. If it's reached, the Binding works. You can also put a converter (IValueConverter) on the Binding (that simply returns the value it receives) and place a breakpoint there.
Use PresentationTraceSources.TraceLevel=High on the Binding to get more verbose tracing (that will appear in the VS Output window).
Are DependentAxis/IndependentAxis defined as dependency properties on FastLineSeries?
Hope that helps,
Aelij.
You've probably already checked this but I find that when I'm debugging bindings the first and easiest place to start is running a debug session from VS as the debug output tells which objects and properties are failing to bind. I usually end up discovering I need to explicitly set a DataContext or something else like a typo. The output to look for start like this:
System.Windows.Data Error: 39 :
BindingExpression path error:
this is followed by the property name you tried to bind to and usually most importantly the class against which its actually trying to bind. If this doesn't help there's a great article here on the debugging bindings: http://www.beacosta.com/blog/?p=52 which discusses the use of PresentationTraceSources.TraceLevel=High which Aelij mentioned, as well as a few other techniques. Hope this gets onto the right track.
Regards,
Mike

Categories

Resources