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/
Related
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);
...
I want to be able to dynamically set a window's size in WPF, and then create as many columns and rows in a grid as I want.
I've created a method for it but it does not seem to work. I also added a border in XAML to see if there are columns and rows but I only see one square.
Also, it throws no error whatsoever.
This is my method:
public void CreateField(Grid MainGrid, TextBox Columns, TextBox Rows, TextBox WHeight, TextBox WWidth, MainWindow MainWindow)
{
int ColumnCount = Int32.Parse(Columns.Text);
int RowCount = Int32.Parse(Rows.Text);
int WindowHeight = Int32.Parse(WHeight.Text);
int WindowWidth = Int32.Parse(WWidth.Text);
MainWindow.MainWindow1.Height = WindowHeight;
MainWindow.MainWindow1.Width = WindowWidth;
for(int a = 0; a <= ColumnCount; a++){
ColumnDefinition c = new ColumnDefinition();
c.Width = new GridLength(WindowWidth / ColumnCount, GridUnitType.Pixel);
MainGrid.ColumnDefinitions.Add(c);
}
for (int a = 0; a <= RowCount; a++)
{
RowDefinition r = new RowDefinition();
r.Height = new GridLength(WindowHeight / RowCount, GridUnitType.Pixel);
MainGrid.RowDefinitions.Add(r);
}
}
In XAML I have this good with only 1 column and 1 row and a dockpanel for the textboxes and buttons.
<Border BorderBrush="Black" BorderThickness="2">
<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>
</Border>
And according to the parameters written here the method should be executed on button click. But instead of a grid with 16 columns and 8 rows, I only get 1 column 1 row. (you can see the border at the edge)
So what am I doing wrong here? I have no real experience with grids whatsoever, and I am pretty clueless. Hope someone can help me out.
EDIT:
The suggestion of to activate MainGrid.ShowGridLines as said by ASh worked. Did not know about this functionality. As it turns out i do have succesfully created the columns and rows. I thought it not to work because i tried to paint a field in the grid with a color which did not work. Now i wonder, why this does not work as i thought this to be correct code.
var converter = new System.Windows.Media.BrushConverter();
var brush1 = (Brush)converter.ConvertFromString("#FFFFFFF0");
DockPanel myDockPanel = new DockPanel();
Grid.SetColumn(myDockPanel, 3);
Grid.SetRow(myDockPanel, 3);
myDockPanel.Background = brush1;
There was a lot going wrong here and its a magical jar of wonderment why you wanted to do this, however this should point you in a better direction
Some Modifications
Dedicated Grid
Get the ActualHeight and ActualWidth to use
Set GridLines true, so you can see whats happening
Set the Grid alignments to Stretch
Don't alter the size of the window
Xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="0"
Grid.Column="0"
BorderBrush="Black"
BorderThickness="2">
<DockPanel Grid.RowSpan="8" Background="LightSalmon">
<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"
Click="ButtonCreate_OnClick"
Content="Create" />
</StackPanel>
</DockPanel>
</Border>
<Grid Name="MainWindowGrid"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ShowGridLines="True" />
</Grid>
Code behind
public void CreateField(Grid MainGrid, TextBox Columns, TextBox Rows) // TextBox WHeight, TextBox WWidth, MainWindow MainWindow)
{
var ColumnCount = int.Parse(Columns.Text);
var RowCount = int.Parse(Rows.Text);
var width = MainGrid.ActualWidth;
var height = MainGrid.ActualHeight;
for (var a = 0; a <= ColumnCount; a++)
{
var c = new ColumnDefinition();
c.Width = new GridLength(width / ColumnCount, GridUnitType.Pixel);
MainGrid.ColumnDefinitions.Add(c);
}
for (var a = 0; a <= RowCount; a++)
{
var r = new RowDefinition();
r.Height = new GridLength(height / RowCount, GridUnitType.Pixel);
MainGrid.RowDefinitions.Add(r);
}
}
private void ButtonCreate_OnClick(object sender, RoutedEventArgs e)
{
CreateField(MainWindowGrid, txtColums, txtRows);
}
grid layout is correct. set MainGrid.ShowGridLines=true; to see it
to see dynamically created control you should add it to grid:
MainGrid.Children.Add(myDockPanel);
since grid rows have equal height and columns have equal width, the following lines can be safely removed:
r.Height = new GridLength(WindowHeight / RowCount, GridUnitType.Pixel);
c.Width = new GridLength(WindowWidth / ColumnCount, GridUnitType.Pixel);
if Width/Height is not set, it is defaulted to * which means Grid will size them equally.
as an alternative UniformGrid can be used:
AnotherMainGrid = new UnifromGrid {Rows = RowCount, Columns = ColumnCount };
but in this case child elements must be added consequtively
I'm adding a stackpanel to a mapcontrol. like below
But some of the points added before are on the top of my stackpanel. How to set my stackpanel topmost?
XAML:
<Grid x:Name="gridMain">
<maps:MapControl
x:Name="mapControl"
ZoomInteractionMode="GestureAndControl"
TiltInteractionMode="GestureAndControl"
RotateInteractionMode="GestureAndControl">
<!--ZoomLevel="{x:Bind ViewModel.ZoomLevel, Mode=OneWay}"
Center="{x:Bind ViewModel.Center, Mode=OneWay}"-->
<maps:MapItemsControl x:Name="MapItems">
<maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<Grid Tapped="MagPoint_Tapped" maps:MapControl.NormalizedAnchorPoint="{Binding NormalizedAnchorPoint}" maps:MapControl.Location="{Binding Location}">
<Ellipse Canvas.ZIndex="0" Width="{Binding Mag5}" Height="{Binding Mag5}" Fill="{Binding MagColor}"/>
<!--<TextBlock Text="{Binding Mag}"/>-->
</Grid>
</DataTemplate>
</maps:MapItemsControl.ItemTemplate>
</maps:MapItemsControl>
</maps:MapControl>
</Grid>
And add panel code.
StackPanel sp = new StackPanel();
sp.Background = new SolidColorBrush(Colors.White);
sp.CornerRadius = new CornerRadius(15);
sp.BorderBrush = new SolidColorBrush(Colors.LightGray);
sp.BorderThickness = new Thickness(1);
sp.Width = 260;
sp.MinHeight = 180;
sp.Padding = new Thickness(10);
Canvas.SetZIndex(sp, 99999);
mapControl.Children.Add(sp);
Windows.UI.Xaml.Controls.Maps.MapControl.SetLocation(sp, new Geopoint(new BasicGeoposition { Longitude = (double)fi.geometry.coordinates[0], Latitude = (double)fi.geometry.coordinates[1] }));
Windows.UI.Xaml.Controls.Maps.MapControl.SetNormalizedAnchorPoint(sp, new Point(0.5, 1));
Your way of setting the ZIndex wouldn't work because the StackPanel and the items inside MapItemsControl are in different hosts.
With the help of Live Visual Tree, you can find out how exactly they get laid out.
In the screenshot above, the StackPanel's host (i.e. the first Canvas) is placed behind the MapOverlayPresenters host (i.e. the second Canvas where MapItemsControl is inserted). So in order to have the StackPanel sit above them, you will need to manually set the ZIndex of the first Canvas to 1.
Once you understand this, the solution becomes simple -
Loaded += (s, e) =>
{
// GetChildByName comes from
// https://github.com/JustinXinLiu/Continuity/blob/0cc3d7556c747a060d40bae089b80eb845da84fa/Continuity/Extensions/UtilExtensions.cs#L44
var layerGrid = mapControl.GetChildByName<Grid>("LayerGrid");
var canvas1 = layerGrid.Children.First();
Canvas.SetZIndex(canvas1, 1);
};
Hope this helps!
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".