I need to return a Text as a System.Window.Shapes Shape (dont ask why, its the task). Since there is no Text-Shape, I thought I could add a Text to an transparent Rectangle, but turns out, with all what I found on the net, the produced text just does not look nice but what is even worse: its drawn with some nasty artefacts:
It kind of looks like the upper top of the Text is cut and appended at the bottom (like some overflow of text)
this is the way I've done it (Need to add text to rectangle):
Rectangle r = new Rectangle();
r.Stroke = Brushes.Transparent;
r.StrokeThickness = 1;
r.VerticalAlignment = VerticalAlignment.Top;
r.HorizontalAlignment = HorizontalAlignment.Left;
r.Margin = new Thickness(0);
r.Width = 200;
r.Height = 200;
r.RenderTransform = new TranslateTransform(100, 100);
TextBlock TB = new TextBlock();
TB.Text = "TEST";
var ft = new FormattedText(
TB.Text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Verdana"),
16,
Brushes.Black);
r.Width = ft.Width;
r.Height = ft.Height;
//The next two magical lines create a special brush that contains a bitmap rendering of the UI element that can then be used like any other brush and its in hardware and is almost the text book example for utilizing all hardware rending performances in WPF unleashed 4.5
BitmapCacheBrush bcb = new BitmapCacheBrush(TB);
r.Fill = bcb;
Can somebody explain why this is happening and maybe give a workaround? Tried to play with Thickness, Padding, etc. but it didnt give the desired result.
I would appreciate any help! Thanks already
It's very easy to achieve what you want using MVVM and datatemplates. Below is a simplified demo to give you idea.
xaml:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:TextItem}">
<TextBlock Text="{Binding Text}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ShapeItem}">
<ContentControl Content="{Binding Shape}" />
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}" />
<Setter Property="Canvas.Top" Value="{Binding Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
cs:
public class Item
{
public double Left { get; set; }
public double Top { get; set; }
}
public class ShapeItem : Item
{
public Shape Shape { get; set; }
}
public class TextItem : Item
{
public string Text { get; set; }
}
public partial class MainWindow : Window
{
public List<object> Items { get; } = new List<object>
{
new ShapeItem { Shape = new Rectangle { Width = 100, Height = 100, Fill= Brushes.Yellow, Stroke = Brushes.Red }, Left = 10, Top = 10 },
new TextItem { Text= "Lalala", Left = 50, Top = 50 },
};
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
Here is the output:
It's not 100% MVVM (for the sake of simplicity), but idea is to have different data template for text and for shapes. They have different viewmodels: TextItem and ShapeItem and are visualized as TextBlock or ContentControl correspondingly.
Another thing is to use ItemsControl to be able to bind to collection of Items and set Canvas.Left/Canvas.Top properties.
ToDo: use ObservableCollection, implement INotifyPropertyChanged for viewmodels, do not use view elements in the viewmodel (brushes, shapes, etc.).
As much as I appreciate all the answers given, I've found the solution:
simply set the fontsize of the Textblock to the same size that you use in formattedText to calculate the Text width and Height:
tb.Fontsize = 16;
...
this way not only does the text "overflow" disappear, but the text looks nice and sharp
Related
I want to develop a Blog application similar design like Universal MSN News App of Microsoft. I want similar design like shown in picture below.
I looked into this News app and found that lots of cool features is integreted with Pivot design.
Few Questions I have:
How do I make dynamic card view layout like this in News app. This app has
dynamic Grid View. Some GridView are bigger some are small. How do we adjust these grid view next to each other
even if they have different heights. Are there any samples for this?
Lazy Loading is implemented to load the feeds when we scroll down.
Any idea how to get work done for lazy loading.
Thank you. Any help is appreciated.
You could use some third-party library: The-UWP-Tools-List
It's easy to integrate Marduk.Controls by the following command:
PM> Install-Package Marduk.Controls
You could see my code sample:
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Marduk.Controls"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ScrollViewer>
<controls:WaterfallFlowView x:Name="Panel" ItemSource="{Binding cc}" StackCount="3" DelayMeasure="True">
<controls:WaterfallFlowView.Resizer>
<local:MyItemResizer/>
</controls:WaterfallFlowView.Resizer>
<controls:WaterfallFlowView.ItemContainerStyle>
<Style TargetType="ContentControl">
<Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</controls:WaterfallFlowView.ItemContainerStyle>
<controls:WaterfallFlowView.ItemTemplate>
<DataTemplate>
<Border Height="{Binding Length}" Background="{Binding Brush}" HorizontalAlignment="Stretch">
<TextBlock FontSize="50" Text="{Binding Num}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</DataTemplate>
</controls:WaterfallFlowView.ItemTemplate>
</controls:WaterfallFlowView>
</ScrollViewer>
</Grid>
public sealed partial class MainPage : Page
{
public ObservableCollection<Test> cc { get; set; }
public MainPage()
{
this.InitializeComponent();
cc = new ObservableCollection<Test>();
cc.Add(new Test() {Length=200,Brush= new SolidColorBrush(Colors.Red),Num=1 });
cc.Add(new Test() { Length = 150, Brush = new SolidColorBrush(Colors.Blue), Num = 2 });
cc.Add(new Test() { Length = 100, Brush = new SolidColorBrush(Colors.LightCyan), Num = 3 });
cc.Add(new Test() { Length = 50, Brush = new SolidColorBrush(Colors.SandyBrown), Num = 4 });
this.DataContext = this;
}
}
public class Test
{
public double Length { get; set; }
public SolidColorBrush Brush { get; set; }
public int Num { get; set; }
}
public class MyItemResizer : IItemResizer
{
public Size Resize(object item, Size oldSize, Size availableSize)
{
return new Size(availableSize.Width, oldSize.Height);
}
}
UWPCommunityToolkit
Custom-sized grids
Create your own widget (extend the base widget class) to represent a single grid. The size of the grid will be decided by the amount of text in it or any other factor you wish.
Use a table or grid layout. For Gtk+, see here: https://developer.gnome.org/gtk3/stable/GtkGrid.html
Attach your custom widgets to the layout.
Lazy loading
The scrolling widget of all toolkits I know emit a signal when the user reaches the bottom (or top or left edge or right edge).
Catch that signal and add more custom grid widgets to the layout. You might need to redisplay the contents of the layout which is a bit inefficient; use multithreading perhaps.
In Gtk+ 3, edge-reached is emitted by ScrolledWindow when any of the edge is reached.
This is the general concept. A quick Google search will reveal how your choice of GUI toolkit does the above.
This screenshot is from the mockup of my ideal UI. Right now, this is a DataGridTemplateColumn, with header = "ATTENDEES". I am running into issues creating the layout of this DataGridColumn's cell.
I currently have an ItemsControl bound to a List of strings which are the attendees' emails. If there are too many attendees and the ItemsControls' bounds cannot fit in the cell, then a Button with Content = "See more" should appear at the bottom of the cell, under the last attendee email that can be rendered within in the cell's bounds.
Then once the Button ("See more") is clicked, the row should expand to an appropriate height for the attendees to all be visible, and the "See more" Button should disappear.
I could not wrap my head around a clean implementation with a TemplateSelector, ValueConverter, or DataTrigger in pure XAML since I need to compare the ItemsControls' height against the DataGridRow's height and then perform a modification of the cell's layout at runtime by hiding all the items in the ItemsControl that cannot fit within the cell and then showing at Button below it.
I concluded on attempting to do this in the code-behind by subscribing to the ItemControls' load event. I first attempted to use the Height, MaxHeight, DesiredSize.Height, RenderedSize.Height, and ActualSize.Height properties of the ItemsControl but those all were equal to the clipped height of the ItemsControl, not the intrinsic height of all its contents.
I am now measuring the total height of all its items' strings using the FormattedText class. Then I compare this summed height with the row's height and that's as far as I have progressed; I am unsure of how to next change the layout of the cell or if this is even the correct approach.
I feel like I am fighting against the design of the WPF framework by doing rudimentary calculations and crude layout changes to the view in the code-behind.
Any help on this would be greatly appreciated!
Here is my event handler for the ItemsControl.Load:
private void AttendeesItemsControl_Loaded(object sender, RoutedEventArgs e)
{
if (currentRowIndex == -1)
{
return;
}
List<ModelBase> eventsData = ModelManager.events.data;
var eventObj = (Event)eventsData[currentRowIndex];
var attendees = eventObj.attendees;
var totalItemsHeight = 0;
for(int i = 0; i < attendees.Count; i++)
{
totalItemsHeight += heightOfString(attendees[i]);
}
var itemsControl = (ItemsControl)sender;
var controlRenderHeight = itemsControl.RenderSize.Height;
// Check if the intrinsic height is greater than what can be drawn inside the cell
if (controlRenderHeight < totalItemsHeight)
{
var itemHeight = totalItemsHeight / attendees.Count;
var visibleItemsCount = controlRenderHeight / itemHeight;
// .... not sure how to proceed
}
}
And the helper function that measures the height of one of its items:
private int heightOfString(string candidate)
{
var fontFamily = new FontFamily("Lato");
var fontStyle = FontStyles.Normal;
var fontWeight = FontWeights.Normal;
var fontStretch = FontStretches.Normal;
var fontSize = 12;
var typeFace = new Typeface(fontFamily, fontStyle, fontWeight, fontStretch);
var formattedText = new FormattedText(candidate, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeFace, fontSize, Brushes.Black);
return (int)formattedText.Height;
}
Finally, this is the DataGridTemplateColumn's XAML, with the cell template definition:
<DataGridTemplateColumn Header="ATTENDEES" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=attendees}" x:Name="AttendeesItemsControl" Loaded="AttendeesItemsControl_Loaded">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock FontFamily="Lato" FontSize="12" FontWeight="Normal" Text="{Binding}">
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I had to do some real work but I got this set up. Hopefully you can follow it. Here is a screen shot of what it looks like. Obviously i didn't attempt to style it yet. Just getting the resizing. This way you let WPF handle the height of your control you leave it autosized. You just manage your list.
I created a control for the list called AttendeeListControl
<UserControl xmlns:stackoverflow="clr-namespace:stackoverflow" x:Class="stackoverflow.AttendeeListControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="GhostWhite">
<Grid.RowDefinitions>
<RowDefinition Height="37"/>
<RowDefinition Height="*"/>
<RowDefinition Height="23"/>
</Grid.RowDefinitions>
<Label Content="Attendees" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<ListBox Name="listBoxAttendees" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1" />
<Button Content="SeeMore" Name="lblMore" HorizontalAlignment="Left" Margin="10,0,0,0" Grid.Row="2" VerticalAlignment="Top" Click="lblMore_Click"/>
</Grid>
</UserControl>
This is the code behind
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Controls;
namespace stackoverflow
{
/// <summary>
/// Interaction logic for AttendeeListControl.xaml
/// </summary>
///
public partial class AttendeeListControl : UserControl
{
public AttendeeListViewModel vm { get; set; }
public AttendeeListControl()
{
InitializeComponent();
var emails = new List<string>() { "email#gmail.com", "email#aol.com", "email.yahoo.com", "email#msn.com" };
var displayed = new ObservableCollection<string>() { emails[0], emails[1] };
vm = new AttendeeListViewModel()
{
EmailList = emails,
DisplayList = displayed,
Expanded = false
};
DataContext = vm;
listBoxAttendees.ItemsSource = vm.DisplayList;
}
private void lblMore_Click(object sender, System.Windows.RoutedEventArgs e)
{
if (vm.Expanded)
{
//remove all but last 2
do
{
vm.DisplayList.RemoveAt(vm.DisplayList.Count - 1);
} while (vm.DisplayList.Count > 2);
lblMore.Content = "Show More";
}
else
{
//don't want the first 2
for (int i = 2; i < vm.EmailList.Count; i++)
{
vm.DisplayList.Add(vm.EmailList[i]);
}
lblMore.Content = "Show Less";
}
vm.Expanded = !vm.Expanded;
}
}
}
and here is the model i used
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace stackoverflow
{
public class AttendeeListViewModel
{
public bool Expanded { get; set; }
public List<string> EmailList { get; set; }
public ObservableCollection<string> DisplayList { get; set; }
}
}
this was all just put on the mainwindow
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:stackoverflow" x:Class="stackoverflow.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:AttendeeListControl HorizontalAlignment="Left" Margin="55,53,0,0" VerticalAlignment="Top"/>
<local:AttendeeListControl HorizontalAlignment="Left" Margin="340,53,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
I have the follow XAML:
<ContentControl HorizontalAlignment="Left" HorizontalContentAlignment="Left" Content="{Binding TotalReviewWordBlock}" Width="465" Margin="5,10,0,5" Foreground="#FF2D2D2D" Background="White"/>
and its binded to the following property:-
public StackPanel TotalReviewWordBlock
{
get
{
StackPanel st = new StackPanel();
st.Orientation = Orientation.Horizontal;
st.Background = new SolidColorBrush(Colors.White);
Paragraph pgf = new Paragraph();
Run r = new Run();
r.Text = App.Convert("Blah ");
r.FontWeight = FontWeights.Bold;
r.Foreground = new SolidColorBrush(CommonLib.rgbFromHexString("#FF2D2D2D"));
pgf.Inlines.Add(r);
int Rating = (int)(this.userrating * 2);
string ratingReplacement;
(some more code in the property itself...)
Run run = new Run();
run.Text = " " + this.myText;
run.Foreground = new SolidColorBrush(CommonLib.rgbFromHexString("#FF2D2D2D"));
pgf.Inlines.Add(run);
RichTextBox rtb = new RichTextBox();
rtb.TextWrapping = TextWrapping.Wrap;
rtb.Width = 450;
rtb.Blocks.Add(pgf);
st.Children.Add(rtb);
st.Background = new SolidColorBrush(Colors.White);
return st;
}
}
The problem is when the text is too much(say more that a 1000 character), or the height of the stackpanel is a lot, Its background becomes black. Its as if the stackpanel breaks) I noticed this earlier but at that time it was in a listbox and had multiple items to i simply made the width of each item 480, used blank grids instead of margins and it was "covered". But this time its just one big chunk of text(in a Paragraph). Let me know if you need ay other info. Please help!!
I worked around a similar "black stackpanel" problem by splitting the text into paragraphs to form a List<String>. And then that list of strings would be the ItemsSource of a ListBox.
So instead of a very large StackPanel, I ended up with a long ListBox.
I also prevented user interaction in the ListBox and vertical scroll by using IsHitTestVisible="False" and ScrollViewer.VerticalScrollBarVisibility="Disabled"
So, the ListBoxended up as follows:
<ListBox x:Name="listBox" IsHitTestVisible="False" ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="White">
<TextBlock TextWrapping="Wrap" Text="{Binding}"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in code behind:
textSplitInParagraphs = new List<String>();
// add paragraphs to the list...
listBox.ItemsSource = textSplitInParagraphs;
Don't know if it is the correct workaround, but I helped me, after some time of banging my head against the table.
Hope this helps.
I have a WrapPanel which is vertically aligned.
My problem is, whenever an elements overflows, it goes to next column. So the items in First column becomes uniformly aligned vertically.
Example: Suppose I have 170px Height for WrapPanel and 35px Height for items of WrapPanel. So it will show first 4 elements in first column which will be uniformly spaced. Rest items will be transferred to next column and so on. In the 170px height, I want these items to use their required height and leave the extra space as it is. So after 140px, I should get 30px space free.
I am not getting any way to do this from the properties of WrapPanel, As it is having very less properties to support the layout and styling of its items.
How can I do this?
You could use a VariableSizedWrapGrid, just like from the Contoso Cookbook Windows 8 examples. State a MaximumRowsOrColumns there, and it will wrap your items onto another column (or row).
<VariableSizedWrapGrid Orientation="Vertical"
MaximumRowsOrColumns="4" />
Is this what you want?
Here is a example of using UniformGrid that may be close to what you are trying to achieve.
XAML
<Grid>
<ListBox ItemsSource="{Binding GridItemsList}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="2" Columns="4" FlowDirection="RightToLeft" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding ItemName}" />
<StackPanel.LayoutTransform>
<RotateTransform Angle="-90"/>
</StackPanel.LayoutTransform>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.LayoutTransform>
<RotateTransform Angle="-90"/>
</ListBox.LayoutTransform>
</ListBox>
</Grid>
Code Behind
public partial class UniformGridWindow : Window {
public UniformGridWindow() {
//Sample Data
GridItemsList = new List<GridItem> {
new GridItem("Item 1"),
new GridItem("Item 2"),
new GridItem("Item 3"),
new GridItem("Item 4"),
new GridItem("Item 5"),
new GridItem("Item 6"),
new GridItem("Item 7"),
new GridItem("Item 8")
};
InitializeComponent();
this.DataContext = this;
}
public List<GridItem> GridItemsList { get; set; }
}
public class GridItem {
public string ItemName { get; set; }
public GridItem(string itemName) {
this.ItemName = itemName;
}
}
I am creating Dynamic Rectangle and adding into StackPanel. I need to add text to each rectangle. How can I do that?
A Rectangle doesn't have any child content, so you will need to put both controls inside of another panel, such as a grid:
<Grid>
<Rectangle Stroke="Red" Fill="Blue"/>
<TextBlock>some text</TextBlock>
</Grid>
You can also use a Border control, which will take a single child and draw a rectangle around it:
<Border BorderBrush="Red" BorderThickness="1" Background="Blue">
<TextBlock>some text</TextBlock>
</Border>
You say "dynamic rectangle", so it sounds like you are doing this in code. The equivalent C# would look something like this:
var grid = new Grid();
grid.Children.Add(new Rectangle() { Stroke = Brushes.Red, Fill = Brushes.Blue });
grid.Children.Add(new TextBlock() { Text = "some text" });
panel.Children.Add(grid);
// or
panel.Children.Add(new Border()
{
BorderBrush = Brushes.Red,
BorderThickness = new Thickness(1),
Background = Brushes.Blue,
Child = new TextBlock() { Text = "some text" },
});
But if you want a dynamic list of rectangles, you should probably use an ItemsControl:
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="1" Background="Blue">
<TextBlock Text="{Binding Text}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you set the DataContext to a list of objects, this XAML will create a Border with a TextBlock for each one with the text set to the Text property on the object.
First of all you can do this, but not by adding the control. And there is a very good reason to do this, for high speed hardware rendering. You can create a special brush from a UI element that caches itself in hardware and fill the rectangle with this hardware, and it is extremely fast. I will just show the code behind because it is the example I have offhand
Rectangle r = new Rectangle();
r.Stroke = Brushes.Blue;
r.StrokeThickness = 5;
r.SetValue(Grid.ColumnProperty, 1);
r.VerticalAlignment = VerticalAlignment.Top;
r.HorizontalAlignment = HorizontalAlignment.Left;
r.Margin = new Thickness(0);
r.Width = 200;
r.Height = 200;
r.RenderTransform = new TranslateTransform(100, 100);
TextBlock TB = new TextBlock();
TB.Text = "Some Text to fill";
// The next two magical lines create a special brush that contains a bitmap
// rendering of the UI element that can then be used like any other brush
// and it's in hardware and is almost the text book example for utilizing
// all hardware rending performances in WPF unleashed 4.5
BitmapCacheBrush bcb = new BitmapCacheBrush(TB);
r.Fill = bcb;
MyCanvas.Children.Add(r);
You need to add a textual control to your StackPanel, such as Label or TextBlock.