I want to obtain the overflown text (i.e. the substring after the ellipsis) after setting FormattedText.MaxTextWidth and FormattedText.MaxTextHeight. Is there an elegant way to achieve this? This seems especially diffult since FormattedText may contain different font families, font sizes etc.
Hmm, this was a tough one. I can get it very close, but it's not 100% accurate. However, perhaps you can use this as a starting point.
Example Output for this string: "This is some really long text that cannot fit within the width specified!"
The Approach:
Basically I wrote a while loop that would check the actual formatted text's width when I fed it some text. If the width exceeded the width of the one that was displaying the ellipsis, then I would strip out the last character and check again and again and again until it fit.
MainWindow.xaml:
<Window x:Class="GetOverflowTextTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<RadioButton x:Name="radioButtonArial" Content="Arial Size 14" GroupName="Fonts" Click="ArialClick" Margin="5" IsChecked="True"/>
<RadioButton x:Name="radioButtonTimesNewRoman" Content="Times New Roman Size 32" GroupName="Fonts" Click="TimesNewRomanClick" Margin="5"/>
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Long Text Block With Ellipsis (Width 200): " Margin="5" HorizontalAlignment="Right"/>
<TextBlock Grid.Row="0" Grid.Column="1" x:Name="myTextBlock" Width="200" Margin="5" TextWrapping="NoWrap" TextTrimming="CharacterEllipsis" Background="DarkGreen" Foreground="White" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Here's your overflow text: " Margin="5" HorizontalAlignment="Right"/>
<TextBlock Grid.Row="1" Grid.Column="1" x:Name="myOverflowTextBlock" Margin="5" TextWrapping="NoWrap" HorizontalAlignment="Left"/>
</Grid>
<StackPanel Orientation="Horizontal">
</StackPanel>
<StackPanel Orientation="Horizontal">
</StackPanel>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Globalization;
using System.Windows;
using System.Windows.Media;
namespace GetOverflowTextTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private const string TEXT = "This is some really long text that cannot fit within the width specified!";
public MainWindow()
{
InitializeComponent();
this.Loaded += OnLoaded;
this.myTextBlock.Text = TEXT;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
UpdateFont();
}
private void UpdateFont()
{
if (this.radioButtonArial.IsChecked.HasValue && this.radioButtonArial.IsChecked.Value)
{
// Change the font to Arial
this.myTextBlock.FontFamily = new FontFamily("Arial");
this.myTextBlock.FontSize = 14;
}
else
{
// Change the font to Times New Roman
this.myTextBlock.FontFamily = new FontFamily("Times New Roman");
this.myTextBlock.FontSize = 32;
}
// Calculate the overflow text using the font, and then update the result.
CalculateAndUpdateOverflowText();
}
private void CalculateAndUpdateOverflowText()
{
// Start with the full text.
var displayedText = TEXT;
// Now start trimming until the width shrinks to the width of myTextBlock.
var fullFormattedText = new FormattedText(displayedText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(this.myTextBlock.FontFamily, myTextBlock.FontStyle, myTextBlock.FontWeight, myTextBlock.FontStretch), myTextBlock.FontSize, new SolidColorBrush(Colors.Black), 1.0);
while (fullFormattedText.Width > this.myTextBlock.Width)
{
displayedText = displayedText.Remove(displayedText.Length - 1, 1);
fullFormattedText = new FormattedText(displayedText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(this.myTextBlock.FontFamily, myTextBlock.FontStyle, myTextBlock.FontWeight, myTextBlock.FontStretch), myTextBlock.FontSize, new SolidColorBrush(Colors.Black), 1.0);
}
// What you have left is the displayed text. Remove it from the overall string to get the remainder overflow text.
// The reason why I added "- 3" is because there are three ellipsis characters that cover up some of the text that would have otherwise been displayed.
var overflowText = TEXT.Remove(0, displayedText.Length - 3);
// Update the text block
this.myOverflowTextBlock.Text = overflowText;
}
private void ArialClick(object sender, RoutedEventArgs e)
{
UpdateFont();
}
private void TimesNewRomanClick(object sender, RoutedEventArgs e)
{
UpdateFont();
}
}
}
After thinking about this problem for a little more, I've come up with this solution (which returns the index of the first :
/// <summary>
/// Retrieves the index at which the text flows over (the first index that is trimmed)
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static int GetOverflowIndex(FormattedText text)
{
// Early out: No overflow
if (text.BuildHighlightGeometry(new Point(0, 0), text.Text.Length - 1, 1) != null)
return -1;
int sublen = text.Text.Length;
int offset = 0;
int index = 0;
while (sublen > 1)
{
string debugStr = text.Text.Substring(offset, sublen);
index = offset + sublen / 2;
Geometry characterGeometry = text.BuildHighlightGeometry(new Point(0, 0), index, 1);
// Geometry is null, if the character is overflown
if (characterGeometry != null)
{
offset = index;
sublen = sublen - sublen / 2;
}
else
{
sublen /= 2;
}
}
return index;
}
Related
I am working on an interactive app that shows different video's based on different stages, I am using C# and WPF and I am trying to use a MediaElement and change it source but it only let me play 1 video file and will not change to the next video, it will keep playing the same file over and over again, I just can't seem to understand why is this happening, I also couldn't find a solution to this online, I have embedded both video files "Stage8.mp4" and "Stage12.mp4" in my solution explorer, Both video files are set to copy if newer and content, also I don't think there is something wrong in the path because if I switch between them it will play the correct file, it just won't change the source in real time.
here are my codes:
C#:
using System;
using System.Windows;
namespace WPF_Tester
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
string NameChecker;
byte stage = 0;
private void BTN1_Click(object sender, RoutedEventArgs e)
{
NameChecker = "Name: " + TXT1.Text + " Last: " + TXT2.Text;
if (testlistview.Items.Contains(NameChecker))
{
MessageBox.Show("The name already exists.");
}
else
{
testlistview.Items.Add(NameChecker);
}
if (stage == 0)
{
TestElement.Source = new Uri("Stage12.mp4", UriKind.Relative);
TestElement.Play();
stage = 1;
}
if (stage == 1)
{
TestElement.Source = new Uri("Stage8.mp4", UriKind.Relative);
TestElement.Play();
stage = 0;
}
}
}
}
XAML:
<Window x:Class="WPF_Tester.MainWindow"
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:WPF_Tester"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Name:" FontSize="20" Margin="20,40,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"></TextBlock>
<TextBox x:Name="TXT1" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="18" Margin="80,40,0,0" MinWidth="30"></TextBox>
<TextBlock Text="Last Name:" Grid.Column="0" Grid.Row="0" FontSize="20" Margin="20,80,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"></TextBlock>
<TextBox x:Name="TXT2" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="18" Margin="120,80,0,0" MinWidth="30"></TextBox>
<Button x:Name="BTN1" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Add Name" FontSize="20" Margin="20,180,0,0" Click="BTN1_Click"></Button>
<!-- here we set controls for the second column.-->
<ListView x:Name="testlistview" Grid.Column="1" Margin="20,40,20,20" FontSize="20" ></ListView>
<MediaElement Name="TestElement" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,200,20,0" UnloadedBehavior="Manual" Source="/Stage12.mp4" LoadedBehavior="Play"></MediaElement>
</Grid>
Thanks in advance and have a great day!
There is an else missing. Your second if statement always resets the file to the first. Adding else should implement the desired toggling.
if (stage == 0)
{
TestElement.Source = new Uri("Stage12.mp4", UriKind.Relative);
TestElement.Play();
stage = 1;
}
//we need an else here, otherwise source is always "Stage12.mp4
else if (stage == 1)
{
TestElement.Source = new Uri("Stage8.mp4", UriKind.Relative);
TestElement.Play();
stage = 0;
}
I was intending to overlay multiple Canvas, between 4 and 6 layers, on top of a large Image, in order that I can set all objects in a given Canvas as visible or invisable with the simplicity of a show or Hide routine in a layer class. UpdateLayers simply has a set of calls to each layer.Update(). In the case of the settlementNames layer, it would seem that the Update code is not doing its job. It is supposed work like this;
private void ShowCities_Click(object sender, RoutedEventArgs e)
{
UpdateLayers();
settlements.Show(Settlements);
settlementNames.Show(SettlementNames);
}
public void Show(Canvas canvas)
{
canvas.Visibility = Visibility.Visible;
}
This worked perfectly with the first canvas containing icon sized BitmapImages at ZIndex 1 (the large Image is essentially the background with ZIndex 0). When I tried to add a further canvas at ZIndex 2, the code steps through as expected but does not show the contents. This time the contents is a set of TextBlocks.
The AssociatedCanvas property in the code, has been checked and is the correct Canvas instance, which was laid down in the XAML main window.
public void Update(string layerSelectSqlQuery, LayerType layerType)
{
DataTable layerDataTable = null;
int x = -1;
int y = -1;
string label;
using (MySqlClientWrapper db = new MySqlClientWrapper("Server = localhost; Database = tribes;Uid = root;Pwd = xxxxxxxxx;"))
{
// TODO add population column - and filter to those settlements considered cities.
layerDataTable = db.GetDataTable(layerSelectSqlQuery);
}
AssociatedCanvas.Children.Clear();
foreach (DataRow dataRow in layerDataTable.Rows)
{
x = (int)dataRow["MapX"];
y = (int)dataRow["MapY"];
label = dataRow["Name"].ToString();
if (x != -1 && y != -1)
{
switch (layerType)
{
case LayerType.Settlements:
DrawBitmapImage(x, y);
break;
case LayerType.SettlementNames:
WriteLabel(x, y, label, Color.FromRgb(0, 0, 0));
break;
case LayerType.Units:
break;
case LayerType.UnitNames:
break;
default:
break;
}
}
}
}
Public void WriteLabel(int x, int y, string text, Color color)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = text;
textBlock.Foreground = new SolidColorBrush(color);
Canvas.SetLeft(textBlock, x);
Canvas.SetTop(textBlock, y);
AssociatedCanvas.Children.Add(textBlock);
}
The XAML looks like this in part:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!--<Slider Grid.Column="0" Orientation="Vertical" HorizontalAlignment="Left" Minimum="1" x:Name="slider" />-->
<ScrollViewer Name="mapScroller" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid Name="grid" RenderTransformOrigin="0.5,0.5">
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform x:Name="scaleTransform" />
</TransformGroup>
</Grid.LayoutTransform>
<Viewbox Grid.Column="0" Grid.Row="0" >
<Image x:Name="MainMap" UseLayoutRounding="True" Stretch="Fill" HorizontalAlignment="Center" VerticalAlignment="Center"
MouseLeftButtonUp="MainMap_MouseLeftButtonUp" Source="{Binding MainTerrainMap}"></Image>
</Viewbox>
<Canvas x:Name="Settlements" Panel.ZIndex="1" />
<Canvas x:Name="SettlementNames" Panel.ZIndex="2" >
</Canvas>
</Grid>
</ScrollViewer>
</Grid>
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 am trying to make a cryptogram puzzle for Windows 8 Phone.
.net framework is 4.5
I am encountering this following error: 'System.Windows.Controls.Grid' does not contain a definition for 'setRow' at Grid.setRow(txt, x); I want to create textboxes dynamically, without using XAML...
To the best of my knowledge, Windows.Controls.Grid has a static method Grid.setRow(UIelement, int)..
Here is MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
using System.Windows.Resources;
using System.IO;
namespace CryptogramPuzzle
{
public partial class MainPage : PhoneApplicationPage
{
List<string> quotes;
List<TextBox> tb;
int length = 0;
int width;
int height;
// Constructor
public MainPage()
{
InitializeComponent();
Random r = new Random();
string encodedStr;
InitializeComponent();
getQuotes();
int rInt = r.Next(0, quotes.Count()); //generates random index for a quote selection
encodedStr = Encryption.encode(quotes[rInt]);
length = encodedStr.Length;
createTxtBox(0, 0);
}
/// <summary>
/// Loads quotes from the text file
/// </summary>
public void getQuotes()
{
quotes = new List<string>();
try
{
StreamResourceInfo sInfo = Application.GetResourceStream(new Uri("/CryptogramPuzzle;component/Resources/Puzzles.txt", UriKind.Relative));
StreamReader sr = new StreamReader(sInfo.Stream);//feeds the reader with the stream
string line;
while ((line = sr.ReadLine()) != null)
{
quotes.Add(line);
}
sr.Close();
// System.Console.WriteLine(quotes[0]);
}
catch (Exception e)
{
throw (e);
}
}
public void createTxtBox(int x, int y)
{
TextBox txt = new TextBox();
RowDefinition newRow = new RowDefinition();
newRow.Height = new GridLength(0, GridUnitType.Auto);
ContentPanel.RowDefinitions.Add(newRow);
txt.MinHeight = 10;
txt.MinHeight = 10;
ContentPanel.Children.Add(txt);
Grid.setRow(txt, x);
}
}
}
Here is my MainPage.xaml:
<phone:PhoneApplicationPage
x:Class="CryptogramPuzzle.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- LOCALIZATION NOTE:
To localize the displayed strings copy their values to appropriately named
keys in the app's neutral language resource file (AppResources.resx) then
replace the hard-coded text value between the attributes' quotation marks
with the binding clause whose path points to that string name.
For example:
Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"
This binding points to the template's string resource named "ApplicationTitle".
Adding supported languages in the Project Properties tab will create a
new resx file per language that can carry the translated values of your
UI strings. The binding in these examples will cause the value of the
attributes to be drawn from the .resx file that matches the
CurrentUICulture of the app at run time.
-->
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
<TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
</Grid>
<!--Uncomment to see an alignment grid to help ensure your controls are
aligned on common boundaries. The image has a top margin of -32px to
account for the System Tray. Set this to 0 (or remove the margin altogether)
if the System Tray is hidden.
Before shipping remove this XAML and the image itself.-->
<!--<Image Source="/Assets/AlignmentGrid.png" VerticalAlignment="Top" Height="800" Width="480" Margin="0,-32,0,0" Grid.Row="0" Grid.RowSpan="2" IsHitTestVisible="False" />-->
</Grid>
Please help me to fix this error, getting pretty desperate!
Thank you.
If it's a standard framework method, I'm pretty sure standard Pascal-casing rules (the guidelines that MS follows at least) dictate it would be called SetRow and not setRow.
And indeed it is.
I want to create a game, where the word is given, but there's one letter missing and you need to choose from one of the letters given below. Being a beginner with C#, I find very difficult to make this work. Right now, I have a word class, which has WordFull, LetterA, LetterB, LetterC, index (where I need to put the letter in) and a CorrectLetter. Then, I load this word object, where I put letter one by one in textboxes and if letter's index in the word (h[e]llo = 1) is equal to the current letter's index property (index = 1), then it displays blank underlined textbox. When you click on that letter, then it checks whether that letter is correct with CorrectLetter property and that's the place where I'm stuck. I want to put that letter in place of empty textbox. But how do I choose it? I think I'm doing something wrong.
TL;DR
I want to make a letter game and I need an advice how to do it.
My XAML grid:
<TabItem Name="zaisti" Header="Vykdyti" IsSelected="True">
<Grid Name="Grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="7*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Viewbox Grid.Row="0" Grid.Column="0">
<StackPanel Name="letters" Orientation="Horizontal">
</StackPanel>
</Viewbox>
<Image Grid.Row="0" Grid.Column="1" Name="img" Margin="10" Source="pack://siteoforigin:,,,/pic.jpg"/>
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Button.Click="Grid_Click">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Margin="10">
<Button.Content>
<Viewbox>
<Label Name="Option1" Content="{Binding LetterA}"></Label>
</Viewbox>
</Button.Content>
</Button>
<Button Grid.Column="1" Margin="10">
<Button.Content>
<Viewbox>
<Label Name="Option2" Content="{Binding LetterB}"></Label>
</Viewbox>
</Button.Content>
</Button>
<Button Grid.Column="2" Margin="10">
<Button.Content>
<Viewbox>
<Label Name="Option3" Content="{Binding LetterC}"></Label>
</Viewbox>
</Button.Content>
</Button>
</Grid>
Code behind:
public partial class MainWindow : Window
{
List<Word> Words = new List<Word>()
{
... data ...
};
int index = 0;
public MainWindow()
{
InitializeComponent();
pradzia.IsSelected = true;
zaisti.IsEnabled = false;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
zaisti.IsSelected = true;
zaisti.IsEnabled = true;
letters.Children.Clear();
LoadWord(index);
this.DataContext = Words[index];
}
private void Grid_Click(object sender, RoutedEventArgs e)
{
if (index == Words.Count() - 1) return;
MessageBox.Show((((e.Source as Button).Content as Viewbox).Child as Label).Content.ToString());
if ((((e.Source as Button).Content as Viewbox).Child as Label).Content.ToString() == Words[index].LetterCorrect)
{
letters.Children.Clear();
LoadWord(++index);
this.DataContext = Words[index];
}
}
private void LoadWord(int i)
{
int a = 0;
foreach (char l in Words[i].WordFull)
{
TextBlock letter = new TextBlock();
letter.Foreground = new SolidColorBrush(Colors.Gray);
letter.Text = l.ToString();
letter.Margin = new Thickness(2);
if (Words[i].index == a)
{
letter.Text = ((char)160).ToString() + ((char)160).ToString();
// Create an underline text decoration. Default is underline.
TextDecoration myUnderline = new TextDecoration();
// Create a solid color brush pen for the text decoration.
myUnderline.Pen = new Pen(Brushes.Red, 1);
myUnderline.PenThicknessUnit = TextDecorationUnit.FontRecommended;
// Set the underline decoration to a TextDecorationCollection and add it to the text block.
TextDecorationCollection myCollection = new TextDecorationCollection();
myCollection.Add(myUnderline);
letter.TextDecorations = myCollection;
}
a++;
letters.Children.Add(letter);
}
}
}
Word class:
class Word
{
public string WordFull { get; set; }
public string LetterA { get; set; }
public string LetterB { get; set; }
public string LetterC { get; set; }
public string LetterCorrect { get; set; }
public int index { get; set; }
}
Based on what I'm seeing, I would do the following
move the creation of the individual letter elements (including the underline) into their own methods that return the component to display.
Then when the player picks the correct letter,
find the underline element,
remove it from the letters visual control,
and replace it with the the correct letter element.
edit - based on comment
There are several ways of getting to the elements in the Children collection. If you know the actual element,
letters.Children.Remove(element);
will allow you to remove the specified element, or
letters.Children[index];
will work if you know the index.