I have made the following simple control, which I add to another window as a popup (starts hidden, gets changed to visible on a button click)
<UserControl x:Class="AddGroupsCtrl"
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:local="clr-namespace:ASManager2017"
mc:Ignorable="d" d:DesignWidth="255" Height="413">
<GroupBox x:Name="groupBox" Header=" Active Directory Groups" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch" Height="350" Width="233" BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<Grid Height="auto">
<ListBox x:Name="grouplistBox" HorizontalAlignment="Stretch" Height="auto" Margin="0,0,0,0" VerticalAlignment="Stretch" Width="auto" SelectionMode="Extended" ScrollViewer.VerticalScrollBarVisibility="Visible" Background="{DynamicResource fadeBrush}"/>
<Button x:Name="addButton" Content="Add" HorizontalAlignment="Right" Margin="0,0,22,0" VerticalAlignment="Bottom" Width="75"/>
<Button x:Name="button" Content="X" HorizontalAlignment="Right" Margin="0,0,22,0" VerticalAlignment="Top" Width="20" Height="20" Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}"/>
</Grid>
</GroupBox>
In the code behind this I have the following fragment....
For Each grp As String In gsList
grouplistBox.Items.Add(grp)
Next
Everything seems to work fine, apart from the scroll bar at the side of the listbox. This starts at about half the height of the entire control, and when trying to slide it down the list doesn't scroll, but the scroller shrinks in size until it gets to a small bar, and then starts scrolling to contents of the window. Likewise dragging the bar back up: the listbox will get to the top of the list when the bar is about halfway up the scroll-area, and then the bar will expand.
Can anyone help me correct this behaviour/explain what I am doing wrong?
Thanks.
Pete.
After more experimenting, things are getting weirder....
Imports AshbyTools
Imports System.DirectoryServices.AccountManagement
Imports System.ComponentModel
Public Class AddGroupsCtrl
Dim _domainString As String
Dim _ouString As String
Public Event addClicked(ByVal glist As List(Of String))
<Description("ouString"), DisplayName("OU String"), Category("Data")>
Public Property ouString As String
Get
Return _ouString
End Get
Set(value As String)
_ouString = value
End Set
End Property
<Description("domainString"), DisplayName("Domain String"), Category("Data")>
Public Property domainString As String
Get
Return _domainString
End Get
Set(value As String)
_domainString = value
End Set
End Property
Private Sub button_Click(sender As Object, e As RoutedEventArgs) Handles button.Click
Me.Visibility = Visibility.Hidden
End Sub
Public Sub loadGroups()
grouplistBox.Items.Clear()
Dim groupCTX As PrincipalContext = ADTools.getConnection(domainString, ouString)
Dim gList As List(Of GroupPrincipal) = ADTools.getManagedGroups(groupCTX)
Dim gsList As New List(Of String)
For Each grp As GroupPrincipal In gList
If Not (grp.DistinguishedName.Contains("Staff Groups") Or grp.DistinguishedName.Contains("Subject Groups") Or grp.DistinguishedName.Contains("Tutor Groups")) Then
gsList.Add(grp.DisplayName)
End If
Next
'For n As Integer = 1 To 70
' grouplistBox.Items.Add("item" & n)
'Next
gsList.Sort()
For Each grp As String In gsList
grouplistBox.Items.Add(grp)
Next
End Sub
Private Sub addButton_Click(sender As Object, e As RoutedEventArgs) Handles addButton.Click
Dim ret As New List(Of String)
For Each grp In grouplistBox.SelectedItems
ret.Add(grp.ToString)
Next
RaiseEvent addClicked(ret)
End Sub
End Class
Upon loadGroups() if I replace the
For Each grp As String In gsList
grouplistBox.Items.Add(grp)
Next
with the commented out code, the scrollbar works perfectly.
With the list of strings from gsList, I get the odd behaviour.
I could post the entire project, but since it is hard-coded for our active directory structure it wouldn't compile on another system. (also, as I am just using this to learn WPF and it's an internal tool, I've hard-coded numerous passwords)
Ok, I have solved the issue. And the answer is even more confusing. Turns out that in the code, where I got the DisplayName of the group to add to the list of string, it was returning 'Nothing' for each of them. Yet somehow displayed the list correctly (although with a screwy scrollbar). Now I have changed it to return grp.name rather than displayname, and EVERYTHING works as I'd expect.
Related
I'm attempting to implement Excel-like column filtering and sorting. To do this, I used a DataTemplate to define the Column Header.
<DataGrid x:Name="dataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" CanUserSortColumns="False">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="23"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="ExcelFilterButton" Tag="{Binding}" Click="ExcelFilterButton_Click" Margin="0,0,0,0" BorderThickness="0" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Focusable="False" Grid.Column="0">
<Image Source="Resources\NoSortNoFilter.png" Width="19" Height="19" />
</Button>
<TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
And it comes out nicely.
I tried using VisualTreeHelper to find the image from the Column Header, but the Header property is a string. I've tried using the HeaderStyle and HeaderTemplate properties also but to no avail.
Using a WPF Spy program called Snoop, I can see the image in there, but still can't figure out how to access it in code. The reason I need to access it in code is to change the image based on whether that column is sorted and/or filtered. (Could this be done in XAML?)
Ok, I figured out how to do it. This most likely not the right way to do it, but I found a way that works.
To give you a little about the process.
The user clicks a header button. The buttons Tag property is bound to the column header.
The click event handler instantiates the context menu and sets its Tag to equal the button Tag.
The user clicks on a menu item.
The event handler sends the Context Menu Tag property and image name to the routine that finds the button, and then the image in the button, and changes the image.
now for the code.
The button click event handler:
Private Sub ExcelFilterButton_Click(sender As Object, e As RoutedEventArgs)
With DirectCast(Resources("sortContextMenu"), ContextMenu)
.Tag = DirectCast(sender, Button).Tag
.IsOpen = True
End With
End Sub
The menu item click event handler
Private Sub ContextMenuItem_Click(Sender As Object, e As RoutedEventArgs)
If TypeOf Sender Is MenuItem Then
'just testing, of course this isn't all this handler does.
SetColumnSortImage(Sender.Tag, "Filtered")
End If
End Sub
The SetColumnSortImage routine, which calls the two following routines.
Private Sub SetColumnSortImage(Tag As String, ImageName As String)
Dim btn As Button = Nothing
GetSortButton(Of Button)(dataGrid, Tag, btn)
If btn IsNot Nothing Then
Dim img As Image = GetChildOfType(Of Image)(btn)
img.Source = New BitmapImage(New Uri("pack://application:,,,/Resources/" & ImageName & ".png"))
End If
End Sub
The GetSortButton routine
Private Sub GetSortButton(Of T As DependencyObject)(dep As DependencyObject, Tag As String, ByRef out As DependencyObject)
If dep IsNot Nothing Then
If TypeOf dep Is Button AndAlso CType(dep, Button).Tag = Tag Then
out = dep
Else
If VisualTreeHelper.GetChildrenCount(dep) > 0 Then
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(dep) - 1
GetSortButton(Of T)(VisualTreeHelper.GetChild(dep, i), Tag, out)
Next
End If
End If
End If
End Sub
This routine was found elsewhere on StackOverflow in C#. I converted it to VB.
Private Function GetChildOfType(Of T As DependencyObject)(depObj As DependencyObject) As T
If depObj Is Nothing Then
Return Nothing
End If
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(depObj) - 1
Dim child = VisualTreeHelper.GetChild(depObj, i)
Dim result = If(TryCast(child, T), GetChildOfType(Of T)(child))
If result IsNot Nothing Then
Return result
End If
Next
Return Nothing
End Function
You may have a better way. Please post if you do.
I have a ListBox1 and a series of TextBoxes from TextBox9 onwards.I can populate the ListBox1 with Items(in sequence).I am trying to transfer those items to TextBoxes in the same order(one item for one TextBox).
I tried this code:
Dim x As Integer = ListBox1.SelectedIndex
For x = 0 To 50
For Each ListBoxItem In ListBox1.Items
ListBox1.SelectedItem = "TextBox" & (x+9) & ".Text"
Next
Next x
The code compiles but nothing happens when i click the button.Can anyone help me out?
Thanks in advance
Venkatraman
One way among others could be using the FrameworkElement.FindName() method, see also the following sample.
FWIW, in WPF using the MVVM pattern makes much more sense than one might expect at first glance, where usually a ViewModel would provide UI content among other things, and you usually would not want to do too much in the View's code-behind.
Sample.XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" SizeToContent="WidthAndHeight"
Loaded="Window_Loaded">
<StackPanel x:Name="stackPanel">
<ListBox x:Name="l1"/>
<TextBox x:Name="t1"/>
<TextBox x:Name="t2"/>
<TextBox x:Name="t3"/>
<Button Content="Test" Height="25" Click="Button_Click"/>
</StackPanel>
</Window>
Sample.VB:
Class MainWindow
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
l1.Items.Add("Item1")
l1.Items.Add("Item2")
l1.Items.Add("Item3")
l1.Items.Add("Item4")
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim i As Integer
Dim s As String
For Each item As String In l1.Items
i = i + 1
s = String.Format("t{0}", i)
Dim control = Me.FindName(s)
If TypeOf control Is TextBox Then
control.Text = item
End If
Next
End Sub
End Class
To anyone that can help or show me a better way. I have tried to do this with an observable collection, a list based on a custom class, global/non-global collections, using the listbox's itemssource, synclocking, and finally emptying and manually entering in items.
It tends to work fine, but every once in a while I get the error "Collection was modified; enumeration operation may not execute." which seems to occur at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()
I have tried a few different things over the past couple of weeks and I am able to avoid it sometimes, but then I can reproduce it in a different area.
Essentially, I have three listboxes Parent, Current, Children. These have captioned images in them.
When an Image is selected the images in the Parent, Current, and Children empty and reload based on the selected image.
I've written it in vb, but I can convert it to C# if that will help. The code has been changed many times and so the latest one has a lot of commented code in it. Any Help or suggestions would be greatly appreciated. Anything to simplify it, get it to work, or performance enhancements would be great.
Imagine the code in triplicate. The code is nearly identical with the exception of names.
<Page.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=CurrentList}"
x:Key="CurrentList" />
</Page.Resources>
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Disabled" PanningMode="VerticalOnly" FlowDirection="RightToLeft" HorizontalScrollBarVisibility="Auto"
>
<ListBox Name="CurrentListbox" VerticalAlignment ="Stretch" Margin=" 8" Background="Transparent"
Height="Auto" BorderThickness="0" HorizontalContentAlignment="Center" FlowDirection="LeftToRight" HorizontalAlignment="Center"
ItemsSource="{Binding CurrentList}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="ListBoxItem.Selected" Handler="CurrentListBoxItem_Selected" HandledEventsToo="false"/>
</Style>
</ListBox.Resources>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="#FF0000AC" Style="{StaticResource ResourceKey=ThumbnailBorder}">
<!--<Viewbox MaxWidth ="100" >-->
<StackPanel>
<Grid>
<Border Name="ItemBorder" Style="{StaticResource ResourceKey=ThumbnailInnerBorder}"/>
<Image Name="PersonImage" MaxHeight ="200" MaxWidth ="200" Source="{Binding ProfileImagePath}"
HorizontalAlignment="Center" >
<Image.OpacityMask>
<VisualBrush Visual="{Binding ElementName=ItemBorder}"/>
</Image.OpacityMask>
</Image>
</Grid>
<Viewbox MaxWidth ="190" Margin="5,0,5,0" MaxHeight="15">
<TextBlock Name="Person" Text="{Binding ProfileName}" Tag="{Binding UserID}" HorizontalAlignment="Center" />
</Viewbox>
</StackPanel>
<!--</Viewbox>-->
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
The Collection is stored in a global Variable and populated using a SQlCe Command.
Public Shared CurrentPeopleList As New CollectionViewSource
Public Shared Current_People_List As New ObservableCollection(Of Currents)()
Public Shared Property CurrentList() As ObservableCollection(Of Currents)
Get
Return Current_People_List
End Get
Set(ByVal value As ObservableCollection(Of Currents))
Current_People_List = value
End Set
End Property
The Custom Class of Currents:
Public Class Currents
Implements INotifyPropertyChanged
Private ProfileNameValue As String
Private UserIDValue As Integer
Private ProfileImagePathValue As String
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
#Region "Properties Getters and Setters"
Public Property ProfileName() As String
Get
Return Me.ProfileNameValue
End Get
Set(ByVal value As String)
Me.ProfileNameValue = value
OnPropertyChanged("ProfileName")
End Set
End Property
Public Property UserID() As Integer
Get
Return Me.UserIDValue
End Get
Set(ByVal value As Integer)
If value < 0 Then
Throw New ArgumentException("User ID must be greater than 0 ")
End If
Me.UserIDValue = value
OnPropertyChanged("UserID")
End Set
End Property
Public Property ProfileImagePath() As String
Get
Return Me.ProfileImagePathValue
End Get
Set(ByVal value As String)
Me.ProfileImagePathValue = RelativeProgramPath() & "Media\Pictures\" & value
OnPropertyChanged("ProfileImagePath")
End Set
End Property
#End Region
Public Sub New(ByVal UserID As Integer, ByVal ProfileName As String, ByVal ProfileImagePath As String)
Me.ProfileNameValue = ProfileName
Me.UserIDValue = UserID
Me.ProfileImagePathValue = If(ProfileImagePath Like "pack://*", ProfileImagePath, RelativeProgramPath() & "Media\Pictures\" & ProfileImagePath)
End Sub
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
End Class
Finally, the way these are populated is by using a common function
Private Sub Reload(ByVal CurrentID As Integer)
Try
Try
ParentList = Nothing
CurrentList = Nothing
ChildrenList = Nothing
Catch
End Try
Try
Me.ParentListbox.ItemsSource = Nothing
Me.CurrentListbox.ItemsSource = Nothing
Me.ChildListbox.ItemsSource = Nothing
Catch
End Try
Try
CurrentPersonID = CurrentID
'Load the Images Based on the people.
Dim Ch = Load_Children_People(CurrentID)
Dim C = Load_Current_People(CurrentID)
Dim P = Load_Parent_People(CurrentID)
ParentList = If(P.Count > 0, P, Nothing)
CurrentList = If(C.Count > 0, C, Nothing)
ChildrenList = If(Ch.Count > 0, Ch, Nothing)
Catch
End Try
Try
If ParentList IsNot Nothing Then _
Me.ParentListbox.ItemsSource = CollectionViewSource.GetDefaultView(ParentList)
If CurrentList IsNot Nothing Then _
Me.CurrentListbox.ItemsSource = CollectionViewSource.GetDefaultView(CurrentList)
If ChildrenList IsNot Nothing Then _
Me.ChildListbox.ItemsSource = CollectionViewSource.GetDefaultView(ChildrenList)
Catch
End Try
Catch
End Try
'For some reason this will not work without a pause. Not sure why.
'FIXME Remove Pause Delay When able.
'Thread.Sleep(200)
End Sub
I used a lot of try-catch's to attempt to catch the error, but have been unsuccessful thus far. My suspicion is that the error occurs during a UI thread that I cannot seem to pin down. As I mentioned earlier. any assistance would be great.
I have a canvas of sorts derived from a 'Panel' used for custom drawing of lines and other geometries, all from VB code. I got this approach from a book and I'm not sure it's the best approach. The drawing part works so far for me.
But what I need is to put a textbox control on the control containing text which can be edited by the user. The textbox needs to be placed at coordinates determined dynamically and later deleted. There will probably be other controls handled so.
The following code does nothing:
tb = New TextBox()
tb.Text = "How now brown cow?"
tb.BorderThickness = New Thickness(3)
tb.BorderBrush = Brushes.CadetBlue
drawingSurface.Children.Add(tb)
This is the definition of my DrawingCanvas:
Public Class DrawingCanvas
Inherits Panel
Private visuals As New List(Of Visual)()
Private hits As New List(Of DrawingVisual)()
Protected Overrides Function GetVisualChild(ByVal index As Integer) As Visual
Return visuals(index)
End Function
Protected Overrides ReadOnly Property VisualChildrenCount() As Integer
Get
Return visuals.Count
End Get
End Property
Public Sub AddVisual(ByVal visual As Visual)
visuals.Add(visual)
MyBase.AddVisualChild(visual)
MyBase.AddLogicalChild(visual)
End Sub
Public Sub DeleteVisual(ByVal visual As Visual)
visuals.Remove(visual)
MyBase.RemoveVisualChild(visual)
MyBase.RemoveLogicalChild(visual)
End Sub
Public Function GetVisual(ByVal point As Point) As DrawingVisual
Dim hitResult As HitTestResult = VisualTreeHelper.HitTest(Me, point)
Return TryCast(hitResult.VisualHit, DrawingVisual)
End Function
Public Function GetVisuals(ByVal region As Geometry) As List(Of DrawingVisual)
hits.Clear()
Dim parameters As New GeometryHitTestParameters(region)
Dim callback As New HitTestResultCallback(AddressOf Me.HitTestCallback)
VisualTreeHelper.HitTest(Me, Nothing, callback, parameters)
Return hits
End Function
Private Function HitTestCallback(ByVal result As HitTestResult) As HitTestResultBehavior
Dim geometryResult As GeometryHitTestResult = CType(result, GeometryHitTestResult)
Dim visual As DrawingVisual = TryCast(result.VisualHit, DrawingVisual)
If visual IsNot Nothing AndAlso geometryResult.IntersectionDetail = IntersectionDetail.FullyInside Then
hits.Add(visual)
MsgBox("Ouch")
End If
Return HitTestResultBehavior.Continue
End Function
End Class
Here is the XAML. I added a textbox to the DrawingCanvas just to see if something appears. Nothing did. In fact, I want to do this in code, not XAML. I thought I could hide or move it around dynamically.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Music"
Title="MainWindow" Height="539" Width="892">
<DockPanel>
<Menu DockPanel.Dock="Top" Name="MainMenu" VerticalAlignment="Top" Height="25">
<MenuItem Name="File" Header="File">
<MenuItem Name="Open" Header="Bla bla..."/>
</MenuItem>
</Menu>
<local:DrawingCanvas DockPanel.Dock="Bottom" x:Name="drawingSurface" RenderTransformOrigin="0.5,0.5" >
<TextBox Height="0" Name="TextBox1" Width="45" Text="How now brown cow?" />
</local:DrawingCanvas>
</DockPanel>
</Window>
Thanks for helping a nooby. A solution would be very useful for me. This was easy with windows forms, but I need the drawing speed of WPF.
I think you are a bit off here. In WPF you have a control called Canvas. I would suggest you use that instead of your own "DrawingCanvas", which I can't get to work btw. :( (For some reason I cant create code blocks so if someone can edit it I would be pleased)
Anyhow,
<local:DrawingCanvas DockPanel.Dock="Bottom" x:Name="drawingSurface" RenderTransformOrigin="0.5,0.5" >
<TextBox Height="0" Name="TextBox1" Width="45" Text="How now brown cow?" />
</local:DrawingCanvas>
Turns into:
<Canvas x:Name="drawingSurface">
</Canvas>
And then to add a textbox just do as your current code:
Dim tb as New TextBox
drawingSurface.Children.Add(tb)
This should give you what you need.
Heres the code for adding a rectangle to your canvas.
Private Sub DrawBackground()
Dim Rect As New Rectangle()
Rect.Height = 50
Rect.Width = 50
Rect.Fill = Brushes.Cornsilk
drawingSurface.SetTop(Rect, 30)
drawingSurface.SetLeft(Rect, 100)
drawingSurface.Children.Add(Rect)
End Sub
I'll add another answer that might be more in the line of what you are looking for. This is a class that inherits from Canvas that will allow you to draw stuff in the same way as say you do in your comment.
I also creates a textbox on a random location when it is created.
Public Class DrawingCanvas
Inherits Canvas
Public RandomTextBox As New TextBox
Protected Overrides Sub OnRender(dc As System.Windows.Media.DrawingContext)
Dim brush As Brush = Brushes.Black
Dim drawingPen As Pen = New Pen(Brushes.Green, 3)
dc.DrawRectangle(brush, drawingPen, New Rect(5, 5, Me.ActualWidth - 5, Me.ActualHeight - 5))
RandomTextBox.Text = "Herpdiderp"
If Not Me.Children.Contains(RandomTextBox) Then
Dim r As New Random()
RandomTextBox.Height = 23
RandomTextBox.Width = 100
Me.SetTop(RandomTextBox, r.Next(0, Me.ActualHeight - RandomTextBox.Height))
Me.SetLeft(RandomTextBox, r.Next(0, Me.ActualWidth - RandomTextBox.Width))
Me.Children.Add(RandomTextBox)
End If
End Sub
End Class
This is not a full answer. #WozzeC, you were right about using the canvas - almost.
I have managed to solve this in xaml alone - I want to eventually solve it in vb.net.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DockPanel HorizontalAlignment="Stretch" Name="DockPanel1" VerticalAlignment="Stretch" >
<Menu Height="23" DockPanel.Dock="Top" Name="Menu1" VerticalAlignment="Top" />
<Canvas Name="Canvas1" Background="Aquamarine">
<TextBox Canvas.Left="118" HorizontalScrollBarVisibility="Disabled" Canvas.Top="81" AcceptsReturn="True" Height="auto" Name="TextBox1" Width="68" Text="Herpdiderp" BorderThickness="0" Background="Aquamarine" />
</Canvas>
</DockPanel>
</Grid>
And here is a piece of code that expands the text as needed. I think it's almost totally cool. It expands both to the right and downwards, as if you're actually typing on the form. It adds a little too much on the right, but it's not visible in this version because the background color is the same.
Here is the event code that expands it to the right.
Imports System.Globalization
Class MainWindow
Private Sub TextBox1_TextChanged(sender As System.Object, e As System.Windows.Controls.TextChangedEventArgs) Handles TextBox1.TextChanged
Dim ft As New FormattedText(TextBox1.Text, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, New Typeface("Verdana"), 16, Brushes.Black)
TextBox1.Width = ft.Width
End Sub
End Class
I tried this with my existing solution and the textbox does not appear. I made the DrawingCanvas into a plain Canvas and commented out all the code referring to the DrawingCanvas. And the textbox does appear. The problem is this: I need the functionality in the DrawingCanvas - which derives from Canvas. But because the baseclass methods are Protected, I can't get to them. I can only use them in a derived class, unless there is another way I don't know about.
Any ideas about how to solve this?
Even a 3rd party one will do.
Thanks
I'm not sure how a ComboBox would display data in this fashion, as it is designed as a single-selection Control.
Maybe you are looking for something like a ListBox or ListView with a SelectionMode of Multiple or Extended?
<ListBox SelectionMode="Multiple" />
<ListBox SelectionMode="Extended" />
There is no native multiselect combobox in WPF. Please check my blog for a simple hack using expression blend to achieve a multi selection on combobox.
http://jobijoy.blogspot.com/2009/02/simple-multiselect-combobox-using.html
The idea is to utilize the Multi-Selection feature of ListBox in to ComboBox by editing the control template.
But for accessing the selected items you might need to use the bellow line in the code.
((ListBox)cmbBox.Template.FindName("lstBox",cmbBox)).SelectedItems
Where cmbBox is your combobox and lstBox is the ListBox inside the controltemaplate.
I used an expander and filled the expander's header with the selection and the content with a list box. The list box is binded to a collection. Whenever user make a selection, I update the header to show what user has selected.
I found this useful information from Codeproject - ComboBoxMultiSelect
I haven't tried it myself as of yet, but would let know about my experience.
In case it is useful to anyone, I've made a rough and ready multi-select ComboBox.
Basically just a TextBlock with a Button, ListBox and a Popup. Easy to build upon I think.
Set to work with selections as list(of String), itemsSource as a list(of String), and raises a selectionsChanges event.
XAML: (user control with design dimensions excluded)
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="18"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border x:Name="bdr" BorderBrush="Gray" BorderThickness="1" Grid.ColumnSpan="2"/>
<TextBlock x:Name="txtValues" MinWidth="50" Background="#F0F0F0" Margin="1,1,0,1" Padding="3,1,0,1">
<TextBlock.ContextMenu><ContextMenu><MenuItem Header="Clear" Click="clearItems"/></ContextMenu></TextBlock.ContextMenu>
</TextBlock>
<Button Grid.Column="1" Click="showListBox">
<Polygon Points="0,2 12,2 6,8" Fill="Black"/>
</Button>
<Popup x:Name="pop" StaysOpen="False" Grid.ColumnSpan="2"
PlacementTarget="{Binding ElementName=bdr}" Closed="pop_Closed">
<ListBox x:Name="items" SelectionMode="Extended"
Width="{Binding ElementName=bdr,Path=ActualWidth}"/>
</Popup>
</Grid>
and code..
Public Class multiCombo
Private _itemsSource As List(Of String)
Private _selections As List(Of String)
Public Event selectionsChanges(sender As Object, e As EventArgs)
Public Property selections As List(Of String)
Get
Return _selections
End Get
Set
_selections = Value
For Each itm In items.Items
If Value.Contains(itm) Then
If Not items.SelectedItems.Contains(itm) Then items.SelectedItems.Add(itm)
Else
If items.SelectedItems.Contains(itm) Then items.SelectedItems.Remove(itm)
End If
Next
txtValues.Text = String.Empty
For Each itm In Value
If txtValues.Text.Length > 0 Then txtValues.Text += ", "
txtValues.Text += itm
Next
End Set
End Property
Public Property itemsSource As List(Of String)
Get
Return _itemsSource
End Get
Set
_itemsSource = Value
items.ItemsSource = Value
End Set
End Property
Private Sub showListBox(sender As Object, e As RoutedEventArgs)
pop.IsOpen = True
End Sub
Private Sub pop_Closed(sender As Object, e As EventArgs)
Dim changed = items.SelectedItems.Count <> selections.Count
If Not changed Then
For Each selItm In items.SelectedItems
If Not selections.Contains(selItm) Then changed = True
Next
End If
If changed Then
selections.Clear()
txtValues.Text = String.Empty
For Each selItm In items.SelectedItems
selections.Add(selItm)
If txtValues.Text.Length > 0 Then txtValues.Text += ", "
txtValues.Text += selItm
Next
RaiseEvent selectionsChanges(Me, Nothing)
End If
End Sub
Private Sub clearItems(sender As Object, e As RoutedEventArgs)
If selections.Count > 0 Then
selections.Clear()
txtValues.Text = String.Empty
items.SelectedItems.Clear()
RaiseEvent selectionsChanges(Me, Nothing)
End If
End Sub
End Class
Another CodeProject with detailed explanations how to create a ComboBox with multiple selectable Checkboxes:
Multi Select ComboBox in WPF