One way to add a dynamic control in code-behind is something like this (obviously):
Button thisButton = new Button();
thisButton.Name = "somecool_btn";
thisButton.Content = "Click Me";
someStackPanel.Children.Add(thisButton);
I used to know how to do this using XAML in code behind for more complex dynamic control creation. Basically by creating a string with xaml and then adding it to the StackPanel (or some other UI element)....
string someXaml = #"<Button x:Name='somecool_btn' Content='Click Me' Width='100' Height='29'></Button>";
Now add someXaml to a StackPanel or something...
You can use XamlReader.Parse(https://learn.microsoft.com/dotnet/api/system.windows.markup.xamlreader.parse?view=netcore-3.1). Below is actual usage of it in my previous project(It's messy).
private readonly string gridTemplate = #"
<Grid xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
Margin=""0,27,0,0"">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Height=""3"" Grid.Column=""0"" Margin=""0,19,0,0"" VerticalAlignment=""Top"" HorizontalAlignment=""Right"" Fill=""#FFD8D8D8"" Width=""700"" StrokeDashArray=""1"" StrokeThickness=""3"" Stroke=""{Binding RelativeSource={RelativeSource Self}, Path=Fill}""/>
<Rectangle Height=""3"" Grid.Column=""1"" Margin=""0,19,0,0"" VerticalAlignment=""Top"" HorizontalAlignment=""Right"" Fill=""#FFD8D8D8"" Width=""700"" StrokeDashArray=""1"" StrokeThickness=""3"" Stroke=""{Binding RelativeSource={RelativeSource Self}, Path=Fill}""/>
<StackPanel Grid.ColumnSpan=""2"" HorizontalAlignment= ""Center"" VerticalAlignment= ""Top"" Margin= ""0"" Height= ""126"" >
<Grid Margin=""70,0,70,40"">
<Ellipse Fill = ""#FF555555"" HorizontalAlignment=""Left"" Width=""41"" Height=""41""/>
<TextBlock FontFamily = ""{DynamicResource keyBoldFont}"" Foreground=""White"" HorizontalAlignment=""Center"" VerticalAlignment=""Center"" Text=""99"" FontSize=""20""/>
</Grid>
<TextBlock FontFamily = ""{DynamicResource keyBoldFont}"" FontSize= ""20"" HorizontalAlignment= ""Center"" Foreground= ""#FF555555"" TextWrapping= ""Wrap""/>
</StackPanel >
</Grid >";
private Grid MakeNewProgressItem(int gridColumn, string text)
{
try
{
var resBase = System.Windows.Markup.XamlReader.Parse(gridTemplate) as Grid;
var rectangleL = resBase.Children[0] as Rectangle;
var rectangleR = resBase.Children[1] as Rectangle;
var sp = resBase.Children[2] as StackPanel;
var gr2 = sp.Children[0] as Grid;
var ellipse = gr2.Children[0] as Ellipse;
var num = gr2.Children[1] as TextBlock;
var tb = sp.Children[1] as TextBlock;
Grid.SetColumn(resBase, gridColumn);
...
Related
I am working on a WPF Window that presents the results of some technical calculations inside a FlowDocumentViewer.
Problem: The FlowDocument and all its content is created from Code-Behind because every calculation differs a bit in terms of headers, shown results, and lines. I use different BlockUIContainers which hold a Resource Grid with some ItemsControls to show the results in a formatted order and add it to a Section and then to the Blocks of the Document, but only the last Block is shown inside the Reader.
I don't understand why after using FlowDoc.Blocks.Add(section) multiple times, only the last Block is shown.
I have created a Resource inside the XAML Code to fill it from Code Behind with the results. I need the output to look like this, e.g.:
M,ed = 70 kNm
Q,ed = 25 kN
N,ed = 30 kN
...
To achieve formatting like this, I created a grid with three columns, each containing an ItemsControl with a DataTemplate TextBlock, which has a Binding to a List<> of results.
<Window x:Class="FaceplateInput.Output"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FaceplateInput"
mc:Ignorable="d"
x:Name="OutputWnd"
WindowStartupLocation="CenterScreen"
Title="Output" Height="1000" Width="700">
<Window.Resources>
<Grid x:Key ="TestGrid" x:Name="Grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ItemsControl x:Name="ItemsLeft" Grid.Column="0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl x:Name="ItemsMid" Grid.Column="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl x:Name="ItemsRight" Grid.Column="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window.Resources>
...
<FlowDocumentScrollViewer x:Name="DocReader" Grid.Column="1" Grid.Row="2" Margin="20" MaxWidth="700">
</FlowDocumentScrollViewer>
</Grid>
</Window>
I want to create the FlowDocument in Code-Behind to have control over formatting, especially because the document is created from a single string that is returned by the calculating class.
I build the following test-method to see how FlowDocuments work. The result is that only the added paragraphs and the last section are shown in the output window.
Code-Behind:
namespace FaceplateInput
{
public partial class Output: Window
{
public string PathToImage;
public string PathToBackground;
public string PathToTXTFile;
public Output()
{
InitializeComponent();
CreateFlowDocument();
}
private void CreateFlowDocument()
{
FlowDocument FlowDoc = new FlowDocument();
List<string> leftStr = new List<string>();
List<string> midStr = new List<string>();
List<string> rightStr = new List<string>();
//for Testing purposes, i create some weired data to fill the Lists
for (int i = 0; i < 10; i++)
{
leftStr.Add($"LeftLine {i + 1}");
midStr.Add("=");
rightStr.Add($"RightLine {i + 1}");
}
//Creating a new Container with Resource Grid as UIElement:
BlockUIContainer cont = new BlockUIContainer((UIElement)this.FindResource("TestGrid"));
Grid child = (Grid)cont.Child;
//setting the sources for the ItemsControl:
ItemsControl items1 = (ItemsControl)child.Children[0];
items1.ItemsSource = leftStr;
ItemsControl items2 = (ItemsControl)child.Children[1];
items2.ItemsSource = midStr;
ItemsControl items3 = (ItemsControl)child.Children[2];
items3.ItemsSource = rightStr;
//adding section holding BlockUIContainer to Document
Section section = new Section();
section.Blocks.Add(cont);
FlowDoc.Blocks.Add(section);
//disconnecting UIContainer from parent to avoid Exception
section.RemoveChild(cont.Child);
cont.Child = null;
//another "testline" to see where it puts it in the document
FlowDoc.Blocks.Add(new Paragraph(new Run("TestString 1\n")));
//all the stuff above again to test
leftStr.Clear();
midStr.Clear();
rightStr.Clear();
leftStr.Add("______");
midStr.Add("_____________________");
rightStr.Add("_____________________");
for (int i = 0; i < 20; i++)
{
leftStr.Add($"Left: {(double)i * 324 / 10}\n");
midStr.Add("=\n");
rightStr.Add($"{(double)i * 13 / 2}\n");
}
BlockUIContainer cont1 = new BlockUIContainer((UIElement)this.FindResource("TestGrid"));
Grid child1 = (Grid)cont1.Child;
items1 = (ItemsControl)child1.Children[0];
items1.ItemsSource = leftStr;
items2 = (ItemsControl)child1.Children[1];
items2.ItemsSource = midStr;
items3 = (ItemsControl)child1.Children[2];
items3.ItemsSource = rightStr;
Section section1 = new Section();
section1.Blocks.Add(cont1);
FlowDoc.Blocks.Add(new Paragraph(new Run("TestString 2\n")));
FlowDoc.Blocks.Add(section1);
FlowDoc.Blocks.Add(new Paragraph(new Run("TestString 3\n")));
DocReader.Document = FlowDoc;
}
//...
}
}
I'd appreciate any tips on how to get this working, and maybe how to improve the code itself, or any ideas for completely different approaches.
Greets and thx, Crawliiee
You are setting the first container to null, so that first section is empty. Remove this line:
cont.Child = null;
An alternative could be to use a ListView with GroupView as in the example
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.listview?view=netframework-4.7.2&f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(System.Windows.Controls.ListView);k(TargetFrameworkMoniker-.NETFramework,Version%253Dv4.7.2);k(DevLang-csharp)%26rd%3Dtrue
My goal is to programmatically set a DockPanel size.
I want it to span from Grid.Column=1, Grid.Row=1, Grid.RowSpan=5
And I know hot to set it statically in xaml, but not in c#.
Explanation to code: In xaml I made a 1row 1 column grid with some textfields and a button in the DockPanel. In when I press the button it should create a grid with as many column/rows as I wrote in the textfields. Then name each column and each row. And know I want to create a Dockpanel on some of these fields but for that I must define where it starts and how far it spans. This is where the problem is.
here is my xaml code how I made it:
<Grid Name="MainWindowGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Name="DockPanel"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Background="LightSalmon" Grid.Row="0" Grid.Column="0" Grid.RowSpan="8">
<StackPanel>
<TextBox Name="txtColums" Text="16"/>
<TextBox Name="txtRows" Text="8"/>
<TextBox Name="txtWindowHeight" Text="800"/>
<TextBox Name="txtWindowWidth" Text="1600"/>
<Button x:Name="ButtonCreate" Content="Create" Click="ButtonCreate_Click"/>
</StackPanel>
</DockPanel>
<ContentControl Content="{Binding}"/>
</Grid>
And my C# code what I have so far:
Methods Methods = new Methods();
Methods.CreateField(MainWindowGrid, txtColums, txtRows, txtWindowHeight, txtWindowWidth, MainWindow1);
int GridColumnCount = MainWindowGrid.ColumnDefinitions.Count;
int GridRowCount = MainWindowGrid.RowDefinitions.Count;
for (int a = 1; a < GridColumnCount; a++)
{
MainWindowGrid.ColumnDefinitions.ElementAt(a).Name = "C" + a;
}
for (int a = 1; a < GridRowCount; a++)
{
MainWindowGrid.RowDefinitions.ElementAt(a).Name = "R" + a;
}
var converter = new System.Windows.Media.BrushConverter();
var brush1 = (Brush)converter.ConvertFromString("#FFFFFFF0");
DockPanel myDockPanel = new DockPanel();
myDockPanel.Background = brush1;
myDockPanel.
At the very end I want to be able to set at which row/column the dockpanel should be and then span it, but I sadly do not know how.
You could use the following methods to set the Grid.Column, Grid.Row and Grid.RowSpan attached properties of myDockPanel:
Grid.SetColumn(myDockPanel, 1); //= <DockPanel ... Grid.Column = "1"
Grid.SetRow(myDockPanel, 1); //= <DockPanel ... Grid.Row = "1"
Grid.SetRowSpan(myDockPanel, 8); //= <DockPanel ... Grid.RowSpan = "8"
While there are a few questions on the site about formatting text in WPF textboxes, this issue only occurs on a small set of computers. If there is a similar question already, please point me to it!
I have a WPF application that is used to get resource usage and perform diagnostic/recovery tasks on remote servers. When a command is run, a textbox is created in c# to display the resulting output. On most computers the text prints out fine. However, on a small handful of computers and a VDI my team uses the output seems to hit a boundary and truncate the last column of my output (see screenshots).
Normal output on success
Output seems to hit a boundary that doesn't occur on most computers
c# for creating tabitem and children including the textbox:
TabItem currentButtonTab = buttonTabControl.SelectedItem as TabItem;
TabItem resultsTab = new TabItem();
TextBox resultsTabText = new TextBox();
Grid resultsGrid = new Grid();
Grid tabLabelGrid = new Grid();
Button closeTabCmd = new Button();
DockPanel tabPanel = new DockPanel();
StackPanel tabLabelPanel = new StackPanel();
Label tabLabel = new Label();
resultsTabText.Style = (Style)Resources["txtStyle"];
//resultsTabText.Margin = new Thickness(5);
//resultsTabText.TextAlignment = TextAlignment.Left;
resultsTabText.SetValue(Grid.ColumnProperty, 0);
resultsTabText.SetValue(Grid.ColumnSpanProperty, 2);
resultsTabText.HorizontalAlignment = HorizontalAlignment.Stretch;
resultsTabText.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
resultsTabText.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
//resultsTabText.FontFamily = new FontFamily("Consolas");
resultsTabText.IsReadOnly = true;
resultsTabText.HorizontalContentAlignment = HorizontalAlignment.Stretch;
resultsTabText.MaxLines = 20;
resultsTabText.Tag = string.Format("resultsText");
tabPanel.Margin = new Thickness(0);
tabPanel.SetValue(Grid.ColumnProperty, 0);
tabPanel.SetValue(Grid.ColumnSpanProperty, 2);
tabPanel.HorizontalAlignment = HorizontalAlignment.Stretch;
tabPanel.VerticalAlignment = VerticalAlignment.Stretch;
resultsTab.Padding = new Thickness(5, 0, 5, 0);
resultsTab.Content = resultsGrid;
resultsTab.Header = tabLabelGrid;
resultsTab.Name = string.Format("resultTab{0}", currentTabCount + 1);
resultsTab.Style = (Style)Resources["TabItemTemplate"];
resultsTab.Focus();
closeTabCmd.Click += clearButton_Click;
closeTabCmd.Tag = resultsTab.Name;
closeTabCmd.Margin = new Thickness(0);
closeTabCmd.Padding = new Thickness(1, -3, 1, -2);
closeTabCmd.VerticalAlignment = VerticalAlignment.Center;
closeTabCmd.HorizontalAlignment = HorizontalAlignment.Right;
closeTabCmd.Content = "X";
closeTabCmd.Background = Brushes.WhiteSmoke;
closeTabCmd.Foreground = Brushes.Red;
tabLabelGrid.Margin = new Thickness(0,-5,0,-5);
tabLabelGrid.Children.Add(tabLabelPanel);
tabLabel.Content = computerName + "-" + buttonName;
tabLabel.Style = (Style)Resources["dynamicLabelStyle"];
tabLabelPanel.Margin = new Thickness(0);
tabLabelPanel.Orientation = Orientation.Horizontal;
tabLabelPanel.Children.Add(tabLabel);
tabLabelPanel.Children.Add(closeTabCmd);
resultsTabControl.SelectionChanged += ResultsTabControl_SelectionChanged;
resultsTabControl.Items.Add(resultsTab);
resultsGrid.Children.Add(tabPanel);
tabPanel.Children.Add(resultsTabText);
XAML showing the style applied and the parent tabcontrol:
<Style x:Key="txtStyle" TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="5"/>
<Setter Property="Padding" Value="10,5"/>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="Black"/>
</Style>
...
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
...
<StackPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button x:Name="copyButton" Style="{StaticResource closeTabButton}"
Click="copyCmd_Click" Content="Copy Current Text"/>
<Button x:Name="closeTabsCmd" Style="{StaticResource closeTabButton}"
Click="closeTabsCmd_Click" Content="Close All"/>
<Button x:Name="copyCmd" Margin="5" Padding="2" Click="copyCmd_Click" Content="Copy" Visibility="Collapsed"/>
</StackPanel>
<TabControl x:Name="resultsTabControl" Visibility="Collapsed" Style="{StaticResource resultsControl}"
ButtonBase.Click="clearButton_Click" SelectionChanged="ResultsTabControl_SelectionChanged">
<TabControl.Background>
<SolidColorBrush Color="#FFF9F9F9" Opacity="0.1"/>
</TabControl.Background>
</TabControl>
</StackPanel>
I've confirmed this happens regardless of .NET version installed. This is absolutely driving me insane. In hopes of keeping this as brief as possible I omitted code above I thought was irrelevant but will gladly post more if needed. any help or guidance would be much appreciated!
Sometimes PowerShell truncates its output very similar to what you're seeing. One way you can fix it is to pipe the command's output through out-string to set the width like this:
your-command | out-string -Width 160
Depending on your scenario, there are other ways to control formatting of powershell output. See https://greiginsydney.com/viewing-truncated-powershell-output/
I have a problem with the performance of the wpf gui.
At first I will explain what I have done.
I read from a Database different chat data, mostly text but sometimes there is an icon in the middle of the text, like a smiley or similar. Or, there are no text just a Image.
I have this all done by using a Flowdocument and use a Textblock with inlines. Oh I forgot, I use wpf, sorry.
Thats work great, BUT at the moment the Flowdocument will be painted to the RichTextbox or FlowdocumentReader, its take a long time and the gui freeze. I have think about Virtualizing but a RichTextBox doesn't use this. So my next idea was to use a Listbox and set as item a Richtextbox for every Chatbubble. A Chat can contain round about 20.000 Chatbubbles.
So now I want to use Databinding but I doesn't find a way to bind the inlines of a Textblock.
So now some code.
<DataTemplate x:Key="MessageDataTemplate" DataType="{x:Type classes:Message}">
<Grid>
<RichTextBox x:Name="rtbChat"
SpellCheck.IsEnabled="False"
VerticalScrollBarVisibility="Auto"
VerticalContentAlignment="Stretch">
<FlowDocument
FontFamily="Century Gothic"
FontSize="12"
FontStretch="UltraExpanded">
<Paragraph>
<Figure>
<BlockUIContainer>
<Border>
<Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="tUser"
Foreground="Gray"
TextAlignment="Right"
FontSize="10"
Grid.Row="0"
Grid.Column="1"
Text="{Binding displayUserName}"/>
<TextBlock x:Name="tTime"
Foreground="Gray"
TextAlignment="Left"
FontSize="10"
Grid.Row="0"
Grid.Column="0"
Text="{Binding sendTime}"/>
<TextBlock x:Name="tMessage"
Foreground="Black"
TextAlignment="Justify"
FontSize="12"
Height="NaN"
TextWrapping="Wrap"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Text="{Binding contentText}" />
<Image x:Name="tImage"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Height="NaN"
Source="{Binding imageSend}"/>
</Grid>
</Border>
</Border>
</BlockUIContainer>
</Figure>
</Paragraph>
</FlowDocument>
</RichTextBox>
</Grid>
</DataTemplate>
So this is not final, I'm porting this from Source-code to xaml and some setters are missing at this moment.
I have benchmark the timings and everything works fine, 10 ms for the sqlite, round about 4 sec for the building of the FlowDocument but up to 5 min to paint the FlowDocument in the RichTextBox. I know that is why the hole box is painted, also the part that is not visible.
I hope that is understandable, if not ask me :)
Here the Source-Code before ported to xaml.
var rtBox = new RichTextBox
{
//IsEnabled = false,
BorderThickness = new Thickness(0, 0, 0, 0)
};
var doc = new FlowDocument();
Contact contact = null;
contact = _mess.remote_resource != "" ? _contacts.Find(x => x._jid == _mess.remote_resource) : _contacts.Find(x => x._jid == _mess.key_remote_jid);
var para = new Paragraph();
//--- Style of the message -----
para.Padding = new Thickness(0);
BlockUIContainer blockUI = new BlockUIContainer();
blockUI.Margin = new Thickness(0, 0, 0, 0);
blockUI.Padding = new Thickness(0);
blockUI.TextAlignment = _mess.key_from_me == 1 ? TextAlignment.Right : TextAlignment.Left;
Border bShadow = new Border();
bShadow.Width = 231;
bShadow.BorderBrush = Brushes.LightGray;
bShadow.BorderThickness = new Thickness(0, 0, 0, 1);
Border b2 = new Border();
b2.Width = 230;
b2.BorderBrush = Brushes.Gray;
b2.Background = Brushes.White;
b2.BorderThickness = new Thickness(0.5);
b2.Padding = new Thickness(2);
Grid g = new Grid();
g.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(150,GridUnitType.Star) });
g.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(80) });
g.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(15) });
g.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(25,GridUnitType.Auto) });
TextBlock tUser = new TextBlock()
{
Foreground = Brushes.Gray,
TextAlignment = TextAlignment.Right,
FontSize = 10,
};
tUser.SetValue(Grid.RowProperty, 0);
tUser.SetValue(Grid.ColumnProperty, 1);
if(contact != null)
tUser.Text = _mess.key_from_me == 1 ? "ich" : (contact._displayName == "" ? Whatsapp.Contacs.convertJidToNumber(_mess.remote_resource) : contact._displayName);
else
{
tUser.Text = Whatsapp.Contacs.convertJidToNumber(_mess.remote_resource);
}
TextBlock tTime = new TextBlock()
{
Foreground = Brushes.Gray,
TextAlignment = TextAlignment.Left,
FontSize = 10,
};
tTime.SetValue(Grid.RowProperty, 0);
tTime.SetValue(Grid.ColumnProperty, 0);
tTime.Text = UnixTime.TimeReturnUnix2DateUtc(_mess.timestamp, timeZone).ToString();
TextBlock tMessage = new TextBlock()
{
Foreground = Brushes.Black,
TextAlignment = TextAlignment.Justify,
FontSize = 12,
Height = Double.NaN,
TextWrapping = TextWrapping.Wrap
};
tMessage.SetValue(Grid.RowProperty, 1);
tMessage.SetValue(Grid.ColumnProperty, 0);
tMessage.SetValue(Grid.ColumnSpanProperty, 2);
for (var i = 0; i < _mess.data.Length; i += Char.IsSurrogatePair(_mess.data, i) ? 2 : 1)
{
var x = Char.ConvertToUtf32(_mess.data, i);
if (EmojiConverter.EmojiDictionary.ContainsKey(x))
{
//Generate new Image from Emoji
var emoticonImage = new Image
{
Width = 20,
Height = 20,
Margin = new Thickness(0, -5, 0, -5),
Source = EmojiConverter.EmojiDictionary[x]
};
//add grafik to FlowDocument
tMessage.Inlines.Add(emoticonImage);
}
else
{
tMessage.Inlines.Add(new Run("" + _mess.data[i]));
}
}
g.Children.Add(tUser);
g.Children.Add(tTime);
g.Children.Add(tMessage);
b2.Child = g;
bShadow.Child = b2;
blockUI.Child = bShadow;
Figure fig = new Figure(blockUI);
fig.Padding = new Thickness(0);
fig.Margin = new Thickness(0);
fig.Height = new FigureLength(0, FigureUnitType.Auto);
para.Inlines.Add(fig);
doc.Blocks.Add(para);
rtBox.Document = doc;
msgList.Add(rtBox);
Greetings and thanks for your help.
One method would be to virtualize using a ListBox, certainly. Arguably better methods would be to dynamically load in the required messages or make your own virtualized control (issues with the default ListBox virtualization include that you have to scroll entire items in a single go to get virtualization working... which can suck a bit from a UX perspective in some cases.)
From the sound of it still taking forever to load, the virtualization you've set up isn't working right...
The main thing that you require to get virtualization working is that you need to have the ScrollViewer inside the ListBox template have CanContentScroll=True. Ie do:
<ListBox ScrollViewer.CanContentScroll="True" .... >
Or give the ListBox a template similar to below:
<ControlTemplate>
<Border BorderBrush="{TemplateBinding Border.BorderBrush}"
BorderThickness="{TemplateBinding Border.BorderThickness}"
Background="{TemplateBinding Panel.Background}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}">
<ScrollViewer Focusable="False"
Padding="{TemplateBinding Control.Padding}"
MaxHeight="{TemplateBinding Control.MaxHeight}"
CanContentScroll="True">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
Also, unless you want to actually select previous messages, maybe a ListBox isn't what you want, and you actually want an ItemsControl? See Virtualizing an ItemsControl? for more on that.
Addition 1 - Smooth Scrolling + Virtualization:
See below - if you also want smooth scrolling, might be worth looking at a TreeView - see http://classpattern.com/smooth-scrolling-with-virtualization-wpf-list.html#.VBHWtfldXSg - though I can't vouch for if this actually works at the moment, just discovered it myself!
Addition 2 - Clarification RE needed elements
As in my comments below, if you're not editing everything, you can get rid of all the tags:
<Grid><RichTextBox><FlowDocument><Paragraph><Figure>
In the data template. You probably can't bind the Text of the message to the contentText in the DataTemplate, and will have to have a bit of behind-the-scenes code to dynamically generate the inlines for the TextBlock.
Addition 3 - How to bind a TextBlock to contain images etc from XAML
Okay, so overall (neglecting some styling), I suggest the following:
<DataTemplate x:Key="MessageDataTemplate" DataType="{x:Type classes:Message}">
<Border>
<Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="tUser"
Foreground="Gray"
TextAlignment="Right"
FontSize="10"
Grid.Row="0"
Grid.Column="1"
Text="{Binding displayUserName}" />
<TextBlock x:Name="tTime"
Foreground="Gray"
TextAlignment="Left"
FontSize="10"
Grid.Row="0"
Grid.Column="0"
Text="{Binding sendTime}" />
<TextBlock x:Name="tMessage"
Foreground="Black"
TextAlignment="Justify"
FontSize="12"
Height="NaN"
TextWrapping="Wrap"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
classes:TextBlockInlineBinder.Inlines="{Binding contentInlines}" />
<Image x:Name="tImage"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Height="NaN"
Source="{Binding imageSend}" />
</Grid>
</Border>
</Border>
</DataTemplate>
Note the line classes:TextBlockInlineBinder.Inlines="{Binding contentInlines}" on the message TextBlock. This is in order to be able to bind to Inlines... Basically, this not a dependency property, so cannot be directly bound to!
Instead, we can use the custom static class TextBlockInlineBinder below to create a static dependency property to add to our TextBlock, which when it is updated, it runs the InlinesChanged method to update the Inlines:
public static class TextBlockInlineBinder
{
#region Static DependencyProperty Implementation
public static readonly DependencyProperty InlinesProperty =
DependencyProperty.RegisterAttached("Inlines",
typeof(IEnumerable<Inline>),
typeof(TextBlockInlineBinder),
new UIPropertyMetadata(new Inline[0], InlinesChanged));
public static string GetInlines(DependencyObject obj)
{
return (string)obj.GetValue(InlinesProperty);
}
public static void SetInlines(DependencyObject obj, string value)
{
obj.SetValue(InlinesProperty, value);
}
#endregion
private static void InlinesChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var value = e.NewValue as IEnumerable<Inline>;
var textBlock = sender as TextBlock;
textBlock.Inlines.Clear();
textBlock.Inlines.AddRange(value);
}
}
Finally, the binding (which I've bound to a contentInlines property on your Message class) will need to be of type IEnumerable<Inline>, ie something like:
public IEnumerable<Inline> contentInlines
{
get {
var inlines = new List<Inline>();
for (var i = 0; i < _mess.data.Length; i += Char.IsSurrogatePair(_mess.data, i) ? 2 : 1)
{
var x = Char.ConvertToUtf32(_mess.data, i);
if (EmojiConverter.EmojiDictionary.ContainsKey(x))
{
//Generate new Image from Emoji
var emoticonImage = new Image
{
Width = 20,
Height = 20,
Margin = new Thickness(0, -5, 0, -5),
Source = EmojiConverter.EmojiDictionary[x]
};
inlines.Add(emoticonImage);
}
else
{
inlines.Add(new Run("" + _mess.data[i]));
}
}
return inlines;
}
}
I have a UserControl in Silverlight 3.
The LayoutRoot grid contains one child, a grid, which is made up of three columns and two rows.
Below is the layout:
<Grid x:Name="LayoutRoot" Background="White">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid x:Name="NavigationGrid" Grid.RowSpan="2" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<!-- Content placed here -->
</Grid>
<Border Background="Transparent" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="0" BorderBrush="Black" BorderThickness="0,0,0,0" Height="38" HorizontalAlignment="Stretch" Width="Auto">
<!-- Content placed here -->
</Border>
<Border Background="Transparent" Grid.Column="2" Grid.Row="1" Grid.RowSpan="2" BorderBrush="Black" BorderThickness="0,0,1,1" Height="Auto" VerticalAlignment="Stretch" Width="38">
<!-- Content placed here -->
</Border>
<Border Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="Auto" Background="White" BorderBrush="Black" BorderThickness="0,0,1,1" Width="Auto">
<!-- Content placed here -->
</Border>
</Grid>
</Grid>
I have functionality that uses an adorner. The adorner attaches itself to a specified framework element.
This functionality is invoked when the user clicks on a button that is located in the upper-right corner of the grid named NavigationGrid. The button containins an icon of a pushpin. This functionality removes the NavigationGrid grid from it's parent's children, and adds it to the children of the LayoutRoot grid. The adorner allows the user to be able to drag the grid around the screen.
If the user clicks on the pushpin button again, the intended functionality is for the grid to be removed from the LayoutRoot children, and to be added back to it's original parent's children, with the Grid.Column, Grid.RowSpan etc. values.
The problem I am running into is when the NavigationGrid grid is initially removed from it's parent's children, all of the other elements in the grid resize etc. This is ok, as it is what I wanted. But, when the grid is placed back into it's parent's children, it's location is not the same as it was originally. I checked the Margin property, and it is set to 0. So, because it's location is not identical to it's original location, I programatically set it's margin to a negative value that puts it back visually to where it was originally. This throws off the location of the other elements, and everything begins to overlap etc.
So, my question is, does anyone know how I can achieve this functionality such that the NavigationGrid grid can be removed from it's parent and later placed back into it's parent, with it's original placement/location being in tact?
Thanks.
Chris
Below is a screenshot of the UI. For obvious reasons, I have blacked out certain parts of the UI. The grid on the left, with the label "Processes", is the grid that the user should be able to 'unpin' and move around, which does work, it's the functionality that places it back in place that creates the problem.
Refer to code behind method below that handles pin/unpin functionality:
public void PinMenu(object parameter)
{
if (_navigationGridPinned)
{
PushPinImagePath = new Uri("../Images/pushpin_pinned.png", UriKind.Relative);
_navigationGridPinned = false;
var e = parameter as MouseButtonEventArgs;
if (!e.IsNull())
{
var grid = ValidationHelper.GetPanelFromVisualTree(Application.Current.RootVisual, "NavigationGrid") as Grid;
if (!grid.IsNull())
{
grid.MeasureAndArrange();
double gridHeight = grid.ActualHeight;
double gridWidth = grid.ActualWidth;
grid.HorizontalAlignment = HorizontalAlignment.Left;
grid.VerticalAlignment = VerticalAlignment.Top;
grid.Margin = new Thickness(0, 0, 0, 0);
var parent = grid.Parent as Grid;
parent.Children.Remove(grid);
var layoutRootGrid = parent.Parent as Grid;
if (!layoutRootGrid.IsNull())
{
_originalOffset = parent.TransformToVisual(layoutRootGrid).Transform(new Point(0, 0));
grid.Height = gridHeight;
grid.Width = gridWidth;
var border = grid.Children[0] as Border;
if (!border.IsNull())
{
border.BorderThickness = new Thickness(1, 1, 1, 1);
var backgroundBrush = App.Current.Resources["GradientBlueBrush"] as LinearGradientBrush;
if (!backgroundBrush.IsNull())
{
border.Background = backgroundBrush;
}
}
layoutRootGrid.Children.Add(grid);
Grid.SetRow(grid, 1);
_adorner = new Adorner();
_adorner.HorizontalAlignment = HorizontalAlignment.Left;
_adorner.VerticalAlignment = VerticalAlignment.Top;
_adorner.AdornedElement = grid as FrameworkElement;
_adorner.adorned_MouseLeftButtonDown((FrameworkElement)grid, e);
}
}
}
}
else
{
_navigationGridPinned = true;
PushPinImagePath = new Uri("../Images/pushpin.png", UriKind.Relative);
var grid = ValidationHelper.GetPanelFromVisualTree(Application.Current.RootVisual, "NavigationGrid") as Grid;
if (!grid.IsNull())
{
var parent = grid.Parent as Grid;
if (parent != null)
{
var mainViewGrid = ValidationHelper.GetPanelFromVisualTree(Application.Current.RootVisual, "MainViewGrid") as Grid;
var parentGrid = mainViewGrid.Parent as Grid;
var layoutRootGrid = parentGrid.Parent as Grid;
var currentOffset = grid.TransformToVisual(layoutRootGrid).Transform(new Point(0, 0));
Point p = new Point(-(currentOffset.X - _originalOffset.X), -(currentOffset.Y - _originalOffset.Y));
parent.Children.Remove(grid);
parent.UpdateLayout();
grid.MeasureAndArrange();
var navBorder = ValidationHelper.GetPanelFromVisualTree(Application.Current.RootVisual, "NavBorder") as Border;
var tabMenuBorder = ValidationHelper.GetPanelFromVisualTree(Application.Current.RootVisual, "TabMenuBorder") as Border;
var processMapBorder = ValidationHelper.GetPanelFromVisualTree(Application.Current.RootVisual, "ProcessMapBorder") as Border;
mainViewGrid.Children.Clear();
var border = grid.Children[0] as Border;
if (!border.IsNull())
{
border.Background = new SolidColorBrush(Colors.Transparent);
border.BorderThickness = new Thickness(1, 0, 1, 1);
}
_adorner.HorizontalAlignment = HorizontalAlignment.Left;
_adorner.VerticalAlignment = VerticalAlignment.Top;
_adorner.Margin = new Thickness(0, 0, 0, 0);
_adorner.AdornedElement = null;
mainViewGrid.Children.Add(tabMenuBorder);
Grid.SetColumn(tabMenuBorder, 2);
Grid.SetRowSpan(tabMenuBorder, 2);
Grid.SetRow(tabMenuBorder, 1);
mainViewGrid.Children.Add(processMapBorder);
Grid.SetColumn(processMapBorder, 1);
Grid.SetRow(processMapBorder, 1);
mainViewGrid.Children.Add(navBorder);
Grid.SetColumnSpan(navBorder, 2);
Grid.SetRow(navBorder, 0);
Grid.SetColumn(navBorder, 1);
grid.Margin = new Thickness(p.X, p.Y, 0, 0);
mainViewGrid.Children.Add(grid);
Grid.SetColumn(grid, 0);
Grid.SetRow(grid, 0);
Grid.SetRowSpan(grid, 2);
}
}
}
}
One strategy might be to wrap your "NavigationGrid" inside of a container grid "ContainerGrid" that is "Visible" when pinned, and "Collapsed" when unpinned. That way, when re-parenting during the pin operation, you can place the "NavigationGrid" back inside the named "ContainerGrid".