Programmatically position control in WPF - wpf

I am creating a karaoke program in WPF. Basically, I have a MediaElement which plays vidioes and such, and a StackPanel atop of that which I use to render stuff on top of the MediaElement.
I am trying to programmatically add a TextBlock to the StackPanel, which is going display the lyrics. The problem is that the TextBlock ends up in the top left corner no matter what I write.
Private LyricLabel As New TextBlock
Sub New(Panel As StackPanel)
With LyricLabel
.Foreground = Brushes.White
.FontFamily = New FontFamily("Verdana")
.FontSize = 20
.HorizontalAlignment = HorizontalAlignment.Stretch
.VerticalAlignment = VerticalAlignment.Bottom
End With
Panel.Children.Add(LyricLabel)
End Sub
Also, I want a ball or something to jump from word to word. Is there a easy way to get the width of each of the words + the space between them, or do I have to calculate that by myself?

It would be a better option to use a Grid and do that entirely in XAML. The Grid has the property that if many elements are in the same cell, they all overlap. So you put the MediaElement and a TextBlock together, with proper alignments and you're done:
<Grid>
<MediaElement/>
<TextBlock Text="{Binding CurrentLyric}" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0 0 0 30"/>
</Grid>
You just have to provide a property for dropping the current text to be shown for the binding to work, and it will adjust it for you.

Related

WPF [VB] Bring selected ListViewItem to front and overlap other ListViewItems

I am making an explorer control in my WPF application using a Treeview and Listview. For the Listview I would like to show the contents of the selected folder using 32x32 pixel icons. To achieve this I have the following XAML which is also truncating long file/folder names within a TextBlock:
<ListView x:Name="LV_Explore"
Grid.Column="2"
BorderThickness="0"
VerticalContentAlignment="Top"
ItemsSource="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
AllowDrop="True">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="SP_ExploreItem"
Width="42">
<Image Source="{Binding LargeIcon, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,2" />
<TextBlock x:Name="TXT_ExploreItem"
Width="42"
Height="42"
TextOptions.TextFormattingMode="Display"
TextAlignment="Center"
TextWrapping="Wrap"
TextTrimming="WordEllipsis"
LineStackingStrategy="BlockLineHeight"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When selecting a ListViewItem I would like for it to overlap the items (files/folders) below rather than the current behaviour which is to increase the height of all of the ListViewItems in the current row of the WrapPanel.
To get to the ListViewItem I am using the following code in order to show all of the text in the TextBlock when an item is selected:
Private Sub LV_Explore_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles LV_Explore.SelectionChanged
If LV_Explore.SelectedItems.Count = 1 Then
Dim LVItem As ListViewItem = TryCast(Me.LV_Explore.ItemContainerGenerator.ContainerFromIndex(Me.LV_Explore.SelectedIndex), ListViewItem)
If LVItem IsNot Nothing Then
Dim BD As Border = TryCast(VisualTreeHelper.GetChild(LVItem, 0), Border)
If BD IsNot Nothing Then
Dim CP As ContentPresenter = TryCast(BD.Child, ContentPresenter)
If CP IsNot Nothing Then
Dim SP As StackPanel = TryCast(LVItem.ContentTemplate.FindName("SP_ExploreItem", CP), StackPanel)
If SP IsNot Nothing Then
Dim TB As TextBlock = TryCast(SP.FindName("TXT_ExploreItem"), TextBlock)
TB.TextTrimming = TextTrimming.None
TB.Height = Double.NaN
End If
End If
End If
End If
End If
End Sub
For reference I want to achieve similar to the behaviour of the desktop e.g: Screenshot 1: Normal state Screenshot 2: Selected state
Whereas I currently have the normal state working fine and the selected state appears like this: Screenshot 3: Listview selected state
I suspect that I may need to change from using a WrapPanel to a Canvas in the ItemsPanelTemplate of the ListView control which will then lose the behaviour of lining up and wrapping items within the Listview automatically. So I am looking for suggestions of the best approach to use here to maintain the layout and to allow me to overlap items when selected.
Thanks,
Jay
As you may realise, something in a listview item is inside a sort of a box and it isn't getting out of that. It will be truncated or clipped if you don't let it grow.
A canvas doesn't clip so that would tick one box of your requirement, you would have to write a custom panel based on a canvas or the base panel control and write your own measure/arrange code.
This is non trivial and any scrolling could make it even more complicated, but you could take a look at people's work:
eg
https://www.codeproject.com/Articles/37348/Creating-Custom-Panels-In-WPF
Alternately, you could use an adorner.
The adorner layer is on top of everything else in your window but still in the same datacontext of whatever it's associated with.
These are not so easy to work with but again you could base your code on someone else's:
http://www.nbdtech.com/Blog/archive/2010/07/12/wpf-adorners-part-4-ndash-simple-and-powerful-system-for.aspx
An adorner would probably be my first candidate, if I follow what you want correctly.
Not many people use vb nowadays and I'm afraid you're going to find pretty much every time you look at samples they are c#.

WPF set new control position relative within a grid

This is somewhat a duplicate of other questions so apologies in advance but I haven't been able to make sense of the existing answers (probably because I'm a WPF newb).
I have a grid within a canvas. The grid is added programmatically, not in xaml and is much smaller than the canvas. I want to programmatically add a control (text box) at the position the user clicks on the grid. The application may or may not be full screen and users screen resolutions may differ.
Currently I'm using a mouse down event and getting a point:
Dim p As Point = Mouse.GetPosition(myGrid)
And then using the point.x and point.y with Canvas.SetLeft and Canvas.SetTop but this only works when the app is full screen and the screen res is consistent.
I know it's bad form to ask for code but please include a snippet in your answer as I've been wrestling with this for some time & going round in circles. I'm using VB but answers in any language will be welcome. Thanks very much.
Grid arranges its children basically mostly on the child's Margin property.
So do this OnClick of your grid:
// dont forget to add an event handler on creating the grid
Grid myGrid = new Grid();
myGrid.MouseDown += myGrid_MouseDown;
private void myGrid_MouseDown(object sender, MouserEventArgs e)
{
Point p = e.GetPosition(myGrid);
TextBox tb = new TextBox();
tb.Margin = new Thickness(p.X, p.Y, 0, 0);
tb.HorizontalAlignment = HorizontalAlignment.Left;
tb.VerticalAlignment = VerticalAlignment.Top; // cuz we set margin on Top and Left sides..
myGrid.Children.Add(tb);
}
Hope it helps :)
I think that the problem is not on your code but in your xaml.
Mouse.GetPosition(myGrid)
should works well.
I think that your Grid is not the same size as your Canvas.
Try something like this:
<DockPanel>
<Canvas x:Name="can">
<Grid Height="{Binding ElementName=can, Path=ActualHeight}" Width="{Binding ElementName=can, Path=ActualWidth}" Background="Red" PreviewMouseDown="Grid_PreviewMouseDown" />
</Canvas>
</DockPanel>

TextTrimming in panel in ScrollViewer

I am trying to set up an element within a WPF application, but cannot get the TextTrimming on the TextBlocks to work properly. This is within a ScrollViewer. I am currently using a DockPanel as the inner container.
I have searched quite a bit, but found no questions addressing this issue.
The XAML for the container:
<Grid>
<ScrollViewer>
<DockPanel Name="listedCharacters" LastChildFill="False"></DockPanel>
</ScrollViewer>
</Grid>
The XAML for the child elements (added by code):
<UserControl …>
<Grid DockPanel.Dock="Top" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding FullName}" TextTrimming="CharacterEllipsis" />
</Grid>
</UserControl>
The first problem is that whether I use the DockPanel or a StackPanel, as the inner container, the child element's width appears to be dictated by its content (the TextBlocks) rather than constrained by the parent ScrollViewer.
The effect I want is for the ellipsis to truncate each TextBlock's content when the Window's grid column (not shown in code) is narrower than the bound text. Basically, a list that scrolls vertically when needed, and trims horizontally (which I thought would have been sufficiently common that the answer would be out there; alas, no).
I believe I need to use my own UserControl for this, as there is a lot more going on than shown her; right-click menus on the item in the list, etc.
The secondary issue, iff the optimal panel to use is the DockPanel, how to apply the DockPanel.Dock="Top" through code (C#) when the elements are dynamically added? Again, I cannot find anything that appears to explain this. (I know it is probably in the wrong place in the sample code above.)

How to remove blank space from marquee list’s first item and last item?

I am using horizontal list view in marquee list. Which is keep moving from right to left.
My code is:
double height = canMain.ActualHeight - marqueeList.ActualHeight;
marqueeList.Margin = new Thickness(0, 0, 0, 0);
doubleAnimation.From = -marqueeList.ActualWidth;
doubleAnimation.To = canMain.ActualWidth;
doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(_marqueeTimeInSeconds));
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(Canvas.Right)"));
_storyBoard.Children.Add(doubleAnimation);
_storyBoard.Begin(marqueeList, true)
But once it move completely there is blank space come until again the first item is not come.
I want to remove the blank space between first items to last item.(Like circular)
If your ListView items don't need mouse or keyboard interaction you can just put a Rectangle with a visual brush to the right of it:
<DockPanel Name="marqueeListTwice">
<ListView Name="marqueeList" .../>
<Rectangle Height="{Binding RenderHeight,ElementName=marqueeList}"
Width="{Binding RenderWidth,ElementName=marqueeList}">
<Rectangle.Fill>
<VisualBrush Visual="{Binding ElementName=marqueeList}" />
</Rectangle.Fill>
</Rectangle>
</DockPanel>
This DockPanel will now appear to have two side-by-side copies of the ListView: The one on the left is the real one, and the one on the right is a Rectangle painted with a picture of it. The VisualBrush painting mechanism is "perfect" in the sense that you cannot tell the painted copy from the real thing.
Now you can animate the "marqueeListTwice" control across your canvas for a distance of marqueeList.ActualWidth. When you get to the end you will appear to be looking at the first item but you will actually be looking at the image of it painted on the rectangle. When you move back to 0, the "real" first item will be visible again but since it is visually identical you won't get so much as a flicker.
If you need individual items to interact with the mouse during the pan, this solution won't work because the painted rectangle won't respond as expected: You'll need logic to slide individual items around instead of sliding a ListView containing all of them. You can still use an animation to do it: Just animate a property of your container Window or UserControl, and use that to calculate your item positioning.

New to WPF - What Control to Use / Getting Started?

I'm a WPF n0ob and I'm struggling with selecting the appropriate control to get the layout I want.
What I'm trying to do is draw a bunch of squares (virtual post-it notes) onto the screen. Each note is going to be a decent size (~150 pixels or so) and there could be hundreds of these notes. I want the whole thing to be scrollable so that you can resize the window however you like and the whole thing should be zoomable.
I've done this and it works.
But what I've done seems awfully wrong....
In the code, I'm dynamically creating post it notes and adding them to a giant canvas. I'm manually doing the math to determine where to place each note and how big the canvas should be. I added some labels at the top and had to go back and add a 'Y Offset' value to push all the squares down. I actually generate three different canvas controls and then add each one of them to a stack panel that is inside of a ScrollViewer. I added a scroll bar and set the the stack panel to zoom in and out as you adjust the bar.
It 'works', but I feel like I'm really not using WPF the way it's meant to be used. I tried achieving the same thing with a grid, but the grid didn't seem to want to size itself appropriately.
Can someone tell me a 'better' way to achieve the same look?
Here's my Xaml code - as you can see; there isn't much to it....
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition />
</Grid.RowDefinitions>
<Slider x:Name="ZoomSlider" Minimum="0.01" Value="1" Maximum="2" Margin="0,0,0,6" />
<ScrollViewer x:Name="MyScroller" Grid.Row="1" HorizontalScrollBarVisibility="Visible" HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
<StackPanel x:Name="TicketsGrid" Background="White" HorizontalAlignment="Center">
</StackPanel>
</ScrollViewer>
</Grid>
And then here is what I'm doing in code (ugly!!!)
For Each myWorkItem As WorkItem In myWorkItems
Dim newRect As New Border
newRect.Width = TicketSizeX
newRect.Height = TicketSizeY
If myWorkItem.State.ToUpper.Contains("HOLD") Then
newRect.Background = New SolidColorBrush(Colors.Purple)
Else
newRect.Background = New SolidColorBrush(Color)
End If
newRect.CornerRadius = New System.Windows.CornerRadius(5)
newRect.BorderThickness = New System.Windows.Thickness(1)
newRect.BorderBrush = New SolidColorBrush(Colors.Black)
Dim myPanel As New StackPanel
newRect.Child = myPanel
Dim lblTitle As New Label
lblTitle.Content = myWorkItem.Id
lblTitle.FontWeight = System.Windows.FontWeights.Bold
Dim lblDesc As New TextBlock
lblDesc.Text = myWorkItem.Title
lblDesc.TextWrapping = TextWrapping.Wrap
myPanel.Children.Add(lblTitle)
myPanel.Children.Add(lblDesc)
newRect.SetValue(Canvas.LeftProperty, CType(((TicketCount Mod TicketsXPerUser) * TicketStepX) + (xOffset * TicketStepX * TicketsXPerUser), Double))
newRect.SetValue(Canvas.TopProperty, CType(((Math.Floor((TicketCount / TicketsXPerUser)) * TicketStepY)) + NameLabelHeight, Double))
myCanvas.Children.Add(newRect)
TicketCount += 1
Next
MyCanvas.Width = (TicketStepX * TicketsXPerUser) * myTFS.SharedCodeTeam.Count
MyCanvas.Height = (CType(((Math.Floor((MaxTicket / TicketsXPerUser)) + 1) * TicketStepY), Double))
TicketsGrid.Children.Add(MyCanvas)
ScrollViewer with an ItemsControl inside.
Bind the ItemsSource property of the ItemsControl to an ObservableCollection<PostIt> (where PostIt is a plain old CLR object with all the info that goes on the post it).
Add a DataTemplate to the ItemsTemplate property of the ItemsControl
Add controls to the DataTemplate and bind them directly to an instance of PostIt
Add PostIt instances to the ObservableCollection<PostIt> in your code.
The ScrollViewer handles all scrolling. That's all you need.
The ItemsControl is designed to bind against a collection. For each instance in the collection, it figures out what DataTemplate to use, creates a copy of the template, sets the root's DataContext to the instance it pulled from the collection, and adds the template to itself. It does this for each instance found in the collection.
In your codebehind, all you need to do is create a bunch of PostIts and add them to the collection. No godawful construction of UI elements like you're doing. Urgh.
If you can grasp this concept, you are a step away from understanding MVVM, the Model-View-Controller pattern in WPF. Read about it, try it out. Its a very simple way of making very complex applications with complex UI but with a minimum of code (and none of that crap you're doing currently).

Resources