I have created an ellipse in Windows Phone 8.1 Silverlight App and UWP both
and I wanted to fill it with animating waves,
For this purpose, I am following this solution
but it is for WPF so I am unable to use some control like "Visual Brush".
I wanted to fill ellipse with wave similar to this (ignore 50% in the image) -
And here is my eliipse
<Ellipse Name="WaveEllipse" Grid.Column="1" Grid.Row="0" VerticalAlignment="Top"
Stroke="{StaticResource PhoneAccentBrush}"
StrokeThickness="4"
Width="225"
Height="225">
</Ellipse>
any alternate on the visual brush?
mainly I wanted to implement it in Windows Phone 8.1 Silverlight, but I will switch to UWP if it is not available on WP platform
Before giving you the code, have a look at this animated gif below to try to understand how this animation could be created.
Make sense, right? All we need to do is to create a shape like this, animate its offset X(endlessly) and Y(water level), and finally just clip it with an ellipse.
So first you will need to use Adobe Illustrator or similar tools to create this shape. In AI, there's a Zig Zag effect(see screenshot below) that's perfectly for this. You just need to make sure the starting point is at the same position as the ending one, so when you repeat the animation, it will feel like it's never ending.
What's currently missing in UWP is the ability to clip a UIElement with a non-rectangular shape, so here we have to export this as a png (otherwise we would export it as a svg and use Path to display it).
Also for the same reason, the clipping part requires a lot of work. Like in Jet Chopper's answer, that's tons of code to just get a surfaceBrush! Not to mention that you will also need to manually handle device lost and app lifecycle.
Thankfully, in Creators Update(i.e. 15063), there's a new API called LoadedImageSurface that creates a CompositionSurfaceBrush by an image uri with a couple of lines' code. In my code example below, you will see that I use this, which means, if you want to support older versions of Windows 10, you will need to replace it with what's in Jet's answer.
Code
The idea is to create a UserControl called WaveProgressControl which encapsulates all the animation logic and exposes a dependency property called Percent that controls the water level.
The WaveProgressControl control - XAML
<UserControl x:Class="WaveProgressControlRepo.WaveProgressControl"
Height="160"
Width="160">
<Grid x:Name="Root">
<Ellipse x:Name="ClippedImageContainer"
Fill="White"
Margin="6" />
<Ellipse x:Name="CircleBorder"
Stroke="#FF0289CD"
StrokeThickness="3" />
<TextBlock Foreground="#FF0289CD"
FontSize="36"
FontWeight="SemiBold"
TextAlignment="Right"
VerticalAlignment="Center"
Width="83"
Margin="0,0,12,0">
<Run Text="{x:Bind Percent, Mode=OneWay}" />
<Run Text="%"
FontSize="22" />
</TextBlock>
</Grid>
</UserControl>
The WaveProgressControl control - Code-behind
private readonly Compositor _compositor;
private readonly CompositionPropertySet _percentPropertySet;
public WaveProgressControl()
{
InitializeComponent();
_compositor = Window.Current.Compositor;
_percentPropertySet = _compositor.CreatePropertySet();
_percentPropertySet.InsertScalar("Value", 0.0f);
Loaded += OnLoaded;
}
public double Percent
{
get => (double)GetValue(PercentProperty);
set => SetValue(PercentProperty, value);
}
public static readonly DependencyProperty PercentProperty =
DependencyProperty.Register("Percent", typeof(double), typeof(WaveProgressControl),
new PropertyMetadata(0.0d, (s, e) =>
{
var self = (WaveProgressControl)s;
var propertySet = self._percentPropertySet;
propertySet.InsertScalar("Value", Convert.ToSingle(e.NewValue) / 100);
}));
private void OnLoaded(object sender, RoutedEventArgs e)
{
CompositionSurfaceBrush imageSurfaceBrush;
SetupClippedWaveImage();
SetupEndlessWaveAnimationOnXAxis();
SetupExpressionAnimationOnYAxisBasedOnPercentValue();
void SetupClippedWaveImage()
{
// Note LoadedImageSurface is only available in 15063 onward.
var imageSurface = LoadedImageSurface.StartLoadFromUri(new Uri(BaseUri, "/Assets/wave.png"));
imageSurfaceBrush = _compositor.CreateSurfaceBrush(imageSurface);
imageSurfaceBrush.Stretch = CompositionStretch.None;
imageSurfaceBrush.Offset = new Vector2(120, 248);
var maskBrush = _compositor.CreateMaskBrush();
var maskSurfaceBrush = ClippedImageContainer.GetAlphaMask(); // CompositionSurfaceBrush
maskBrush.Mask = maskSurfaceBrush;
maskBrush.Source = imageSurfaceBrush;
var imageVisual = _compositor.CreateSpriteVisual();
imageVisual.RelativeSizeAdjustment = Vector2.One;
ElementCompositionPreview.SetElementChildVisual(ClippedImageContainer, imageVisual);
imageVisual.Brush = maskBrush;
}
void SetupEndlessWaveAnimationOnXAxis()
{
var waveOffsetXAnimation = _compositor.CreateScalarKeyFrameAnimation();
waveOffsetXAnimation.InsertKeyFrame(1.0f, -80.0f, _compositor.CreateLinearEasingFunction());
waveOffsetXAnimation.Duration = TimeSpan.FromSeconds(1);
waveOffsetXAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
imageSurfaceBrush.StartAnimation("Offset.X", waveOffsetXAnimation);
}
void SetupExpressionAnimationOnYAxisBasedOnPercentValue()
{
var waveOffsetYExpressionAnimation = _compositor.CreateExpressionAnimation("Lerp(248.0f, 120.0f, Percent.Value)");
waveOffsetYExpressionAnimation.SetReferenceParameter("Percent", _percentPropertySet);
imageSurfaceBrush.StartAnimation("Offset.Y", waveOffsetYExpressionAnimation);
}
}
The MainPage
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<local:WaveProgressControl x:Name="WaveProgressControl" />
<Slider Grid.Row="1"
Margin="24"
Value="{x:Bind WaveProgressControl.Percent, Mode=TwoWay}" />
</Grid>
I have put everything into this sample project and below is a live demo. Enjoy! :)
Here's the UWP sample. You may adjust it as you wish:
<Canvas>
<Ellipse x:Name="Ellipse" Width="256" Height="256" Fill="DarkViolet" Stroke="DeepSkyBlue" StrokeThickness="8"/>
<Border x:Name="VisualBorder" Opacity="0.5"/>
</Canvas>
And code behind:
private async void CreateVisuals()
{
var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
var bitmap = await CanvasBitmap.LoadAsync(CanvasDevice.GetSharedDevice(),
new Uri("ms-appx:///Assets/Wave-PNG-Transparent-Picture.png"));
var drawingSurface =
CanvasComposition.CreateCompositionGraphicsDevice(compositor, CanvasDevice.GetSharedDevice())
.CreateDrawingSurface(bitmap.Size,
DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
using (var ds = CanvasComposition.CreateDrawingSession(drawingSurface))
{
ds.Clear(Colors.Transparent);
ds.DrawImage(bitmap);
}
var surfaceBrush = compositor.CreateSurfaceBrush(drawingSurface);
surfaceBrush.Stretch = CompositionStretch.None;
var maskedBrush = compositor.CreateMaskBrush();
maskedBrush.Mask = Ellipse.GetAlphaMask();
maskedBrush.Source = surfaceBrush;
var sprite = compositor.CreateSpriteVisual();
sprite.Size = new Vector2((float)Ellipse.Width, (float)Ellipse.Height);
sprite.Brush = maskedBrush;
sprite.CenterPoint = new Vector3(sprite.Size / 2, 0);
sprite.Scale = new Vector3(0.9f);
ElementCompositionPreview.SetElementChildVisual(VisualBorder, sprite);
var offsetAnimation = compositor.CreateScalarKeyFrameAnimation();
offsetAnimation.InsertKeyFrame(0, 0);
offsetAnimation.InsertKeyFrame(1, 256, compositor.CreateLinearEasingFunction());
offsetAnimation.Duration = TimeSpan.FromMilliseconds(1000);
offsetAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
surfaceBrush.StartAnimation("Offset.X", offsetAnimation);
}
}
Here's how it looks like:
I have achieved this using a simple solution:
Wave2.png is a extended ( copy pasted the image and added to the end of the first image ) to make it longer.
The solution works on WP8/Store apps/UWP/Silverlight
<Border
Background="White"
VerticalAlignment="Center"
HorizontalAlignment="Center"
CornerRadius="10000"
BorderBrush="Black"
BorderThickness="5">
<Grid>
<Ellipse
x:Name="ellipse"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Height="200"
Width="200">
<Ellipse.Fill>
<ImageBrush
x:Name="WaveImage"
Stretch="None"
ImageSource="wave2.png">
<ImageBrush.Transform>
<CompositeTransform
TranslateY="200"
TranslateX="299" />
</ImageBrush.Transform>
</ImageBrush>
</Ellipse.Fill>
</Ellipse>
<TextBlock
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="HUJ" />
</Grid>
</Border>
And here is the animation code:
<Storyboard
x:Name="AnimateWave">
<DoubleAnimationUsingKeyFrames
RepeatBehavior="Forever"
EnableDependentAnimation="True"
Storyboard.TargetProperty="(Shape.Fill).(Brush.Transform).(CompositeTransform.TranslateX)"
Storyboard.TargetName="ellipse">
<EasingDoubleKeyFrame
KeyTime="0:0:5"
Value="-299" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
Related
I have a button that displays the value from a class that I created. Everything works fine, except for the fact that the button content does not refresh once the value of the binding is changed in the code. If I exit the screen and come back, the value is correct. Staying on the same screen does not refresh the button content.
The button code is shown below.
<Grid x:Name="Task1Grid" Grid.Row="0" Grid.Column="0" Margin="5,0,5,0">
<Grid.RowDefinitions>
<RowDefinition Height=".2*"/>
<RowDefinition Height=".6*"/>
<RowDefinition Height=".2*"/>
</Grid.RowDefinitions>
<Button Grid.Row="1" Style="{StaticResource RoundedButtonStyle}" Tag="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Click="StoplightButton_Click" FontFamily="Global User Interface">
<Button.Content>
<Image Stretch="Uniform" Source="{Binding SelectedRepairOrder.TaskStatusGrid[0], Converter={StaticResource TaskStatusToStopLight}, Mode=OneWay}"/>
</Button.Content>
<Button.Background>
<ImageBrush Stretch="Uniform" ImageSource="{Binding SelectedRepairOrder.TaskStatusGrid[0], Converter={StaticResource TaskStatusToStopLight}, Mode=OneWay}"/>
</Button.Background>
</Button>
<Button x:Name="Task0Time" Tag="0" Style="{StaticResource RoundedButtonStyle}" Visibility="{Binding SelectedRepairOrder.TaskStatusGrid[0].NewTaskstatus, Converter=
{StaticResource TaskStatusToVisibility}}" IsEnabled="{Binding ShowForecastFeatures}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="{Binding SelectedRepairOrder.TaskStatusGrid[0].TmTimecmpltask, Converter={StaticResource TaskCompleteTimeToTime}}" Grid.Row="2" Flyout="{StaticResource Task1Flyout}"/>
<TextBlock Grid.Row="0" Text="{Binding ClientInfo.TasksInfo[0].TaskDescription}" TextAlignment="Center" VerticalAlignment="Bottom" FontSize="28"/>
</Grid>
The flyout code is shown below.
<Border x:Name="StopLightBorder" Background="CornflowerBlue" Grid.Row="1" BorderBrush="White" BorderThickness="2">
<Grid x:Name="StopLightGrid" Margin="5" >
<Grid.Resources>
<converter:TaskStatusToStopLight x:Key="TaskStatusToStopLight"/>
<converter:TaskCompleteTimeToTime x:Key="TaskCompleteTimeToTime"/>
<converter:TaskStatusToVisibility x:Key="TaskStatusToVisibility"/>
<Flyout x:Key="Task1Flyout" >
<ListBox ItemsSource="{Binding ForecastTimes}" Tag="0" SelectionChanged="ForecastTimeChanged"/>
</Flyout>
The code which changes the value for the binding is shown below.
private void ForecastTimeChanged(object sender, SelectionChangedEventArgs e)
{
var timeListBox = (ListBox)sender;
var completeTime = Convert.ToDateTime(e.AddedItems[0].ToString());
var taskNum = Convert.ToInt16(((FrameworkElement)sender).Tag);
var result = checkPreviousTaskTimes(completeTime, taskNum);
switch (result)
{
case ForecastResult.ValidTime:
globalContext.SelectedRepairOrder.TaskStatusGrid[taskNum].TmTimecmpltask = completeTime.ToString();
globalContext.SelectedRepairOrder.TaskStatusGrid[taskNum].DtDateoverride = completeTime.ToString();
globalContext.SelectedRepairOrder.TaskStatusGrid[taskNum].TmTimeoverride = completeTime.ToString();
globalContext.SelectedRepairOrder.TaskStatusGrid[taskNum].SendOverrideForecastTime = true;
globalContext.SelectedRepairOrder.WasChanged = true;
globalContext.SelectedRepairOrder.RecordGrid = "1";
((Popup)((FlyoutPresenter)((FrameworkElement)sender).Parent).Parent).IsOpen = false;
break;
default:
showForecastError(result, completeTime, taskNum);
break;
}
}
The Visibility and IsEnabled both work just fine. Not sure what else I can do at this point. It seems that changing the bound data does not have an effect until you leave the screen. I chased this issue all the way through and saw the changes to the data as well as everything else I expected. The flyout causes the forecasttimechanged method to activate. When we go to save this data to the database, the data is correct. The flyout shows the selected time when viewing it on the screen, which is what I want. I see that highlighted in the flyout.
If there is a better control to use than the button, I am all ears at this point. Here is the tricky part. This forecast time can be set in the application as well as the app you are seeing code from. The app has time in 15 minute increments, but the other program that can update this control can put in any time it wishes.
I know there is some control or parameter that needs to be set in order to make this happen properly, but for the life of me, I cannot find it. I have tried everything for the past 3 days now and nothing works.
Help me please.
I know there is some control or parameter that needs to be set in order to make this happen properly, but for the life of me, I cannot find it. I have tried everything for the past 3 days now and nothing works.
From your code, I guess the problem is that you have not implemented INotifyPropertyChanged for binding property. And your logic is complex, you could realize your feature with the easy way like the follow example.
<Button Content="{Binding SelectItem,Mode=OneWay}">
<Button.Flyout>
<Flyout Placement="Top">
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectItem,Mode=TwoWay}">
</ListBox>
</Flyout>
</Button.Flyout>
</Button>
Bind the button content with SelectItem, And then the button content will be modified automatically if the ListBox SelectedItem changed.
public class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public List<string> Items { get; set; } = new List<string>();
private string selectItem = "Nico";
public string SelectItem { get { return selectItem; } set { selectItem = value; OnPropertyChanged(); } }
public MainPageViewModel()
{
Items.Add("Nico");
Items.Add("Song");
Items.Add("Xiao");
}
I'm trying to keep five set of image(png format) in continuous animation which should occupy full screen and after clicking on that popup it should disappear.
There is same image with different position its feel like it is moving up and down continously. In the below code I have used only one image "good_job.png" as popup .But how to use five different image to show in motion up and down.Any help would be appreciated.
Xaml
<RelativePanel x:Name="contentPanel" Grid.Row="1">
<Canvas RelativePanel.AlignTopWithPanel="True">
<Popup x:Name ="ppup" IsOpen = "False" IsLightDismissEnabled = "True"
Width="420" VerticalAlignment="Top"
>
<Image Source = "Assets/good_job.png" Canvas.ZIndex="1" Width="420" />
</Popup>
<Popup x:Name ="ppup1" IsOpen = "False" IsLightDismissEnabled = "True"
Width="320" VerticalAlignment="Center">
<Image Source = "Assets/wrong_ans.png" Canvas.ZIndex="1" Width="420" />
</Popup>
</Canvas>
</RelativePanel>
<Image x:Name="image1" Source="Assets/LearnColor/Object/ob_0_0.png" Height="150" Width="160" RelativePanel.AlignLeftWithPanel="True" Margin="30,40,0,0" Tapped="image1Tap" d:LayoutOverrides="Width, LeftMargin, RightMargin" />
C# Code
if ((image1.Source as BitmapImage).UriSource == new Uri("ms-appx:///Assets/LearnColor/Object/ob_0_0.png", UriKind.Absolute) && (objNameWritten1.Text == "Banana"))
{
ppup.Height = Window.Current.Bounds.Height;
ppup.IsOpen = true;
mediaElement1.Source = new Uri("ms-appx:///Audio/wow good job.mp3");
mediaElement1.AutoPlay = true;
}
Note: This answers is untested and quickly thrown together. I submitted it because I was asked in the comments.
First create a way to loop through the images.
private int CurrentImageId = 0;
private List<string> Images = new List<string>() { "image1", "image2", "image3", "image4", "image5" };
private string NextImage() {
CurrentImageId = (CurrentImageId + 1) % 5;
return Images[CurrentImageId];
}
Then use a timer to reasign the ImageSource at an interval.
var timer = new Timer(100);
timer.Elapsed += (sender, args) => Images.ImageSource = NextImage();
timer.Start();
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 problem where im trying to use a Telerik Jump List with DataVirtualizationMode.Automatic, but i can't get it to work. The reason why i want to use this, is because i want my app to only download the data(games) which is in the current view of the Jump List control and not the whole data everytime. For example if i have searched for "Batman", and its returning 50 games, i don't want it to download and load all the games, only those i can see in the Jump List control.
Here is a sample of using DataVirtualizationMode.Automatic from Telerik, but i couldn't get it to work with my app: http://www.telerik.com/help/windows-phone/raddataboundlistbox-features-datavirtualization-automatic.html
Below is my Jump List control which i want to use with data virtualization.
MainPage.xaml:
<phone:PivotItem Header="Browse">
<Grid>
<telerikPrimitives:RadTextBox Name="txtSearch" HorizontalAlignment="Left" VerticalAlignment="Top" Height="80" Width="390"/>
<telerikPrimitives:RadImageButton Name="imgBtnSeachGame" VerticalAlignment="Top" HorizontalAlignment="Right" ButtonShape="Ellipse" BorderThickness="2" Margin="0,8,0,0" Click="imgBtnSeachGame_Click"></telerikPrimitives:RadImageButton>
<Controls:RadJumpList Name="jlGameList" ItemsSource="{Binding}" Tap="jlGameList_Tap" Margin="0,90,0,0" DataVirtualizationMode="Automatic">
<Controls:RadJumpList.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="{StaticResource PhoneAccentBrush}"
Padding="{StaticResource PhoneTouchTargetOverhang}"
Margin="0,0,0,0">
<TextBlock Name="tblGameTitle" Style="{StaticResource PhoneTextGroupHeaderStyle}" ManipulationStarted="tblGameTitle_ManipulationStarted" ManipulationCompleted="tblGameTitle_ManipulationCompleted">
<Run Text="{Binding GameTitle}"></Run>
</TextBlock>
</Border>
<Grid Background="#242424" Grid.Row="1">
<Image Name="imgGameList" Margin="0,0,0,0" Stretch="Fill" HorizontalAlignment="Left" VerticalAlignment="Top" Height="96" Width="96">
<Image.Source>
<BitmapImage UriSource="{Binding BoxArtFrontThumb}"
CreateOptions="BackgroundCreation" DecodePixelHeight="96" DecodePixelWidth="96" />
</Image.Source>
</Image>
<TextBlock Margin="110,0,0,0" Text="Platform" FontWeight="Bold" TextWrapping="Wrap" Foreground="YellowGreen" FontSize="{StaticResource PhoneFontSizeNormal}"/>
<TextBlock Name="txtPlatform" Margin="110,20,0,0" Text="{Binding Platform}"></TextBlock>
<TextBlock Text="Release Date" FontWeight="Bold" Margin="110,46,0,0" Foreground="YellowGreen" FontSize="{StaticResource PhoneFontSizeNormal}"/>
<TextBlock Name="txtReleaseDate" Margin="110,66,0,0" Text="{Binding ReleaseDate}"></TextBlock>
<!--</StackPanel>-->
</Grid>
<Grid Grid.Row="2"></Grid>
</Grid>
</DataTemplate>
</Controls:RadJumpList.ItemTemplate>
</Controls:RadJumpList>
</Grid>
</phone:PivotItem>
Below is where i bind my DataContext to my GetGamesListItems ObservableCollection in my GameData class. The imgBtnSearchGame_Click event method is being called when a user have typed for example "Batman" in my textbox txtSearch and tapped the button, it will then send the text to my GetGamesListData method.
MainPage.cs:
GameData gd = new GameData();
public MainPage()
{
InitializeComponent();
jlGameList.DataContext = gd.GetGamesListItems;
}
private void imgBtnSeachGame_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(txtSearch.Text))
{
gd.GetGamesListData(txtSearch.Text, "", "");
}
}
Below is where i download the data in XML for the game name searched for. For example if it is "Batman" it will find and return all games with "Batman". The "BoxArtFrontThumb" Property is where im storing all the images for each game and is using async, because sometimes there can be quite alot of images it has to download and show.
GameData.cs
public void GetGamesListData(string name, string platform, string genre)
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += GetGamesListRequestCompleted;
webClient.DownloadStringAsync(new Uri("http://thegamesdb.net/api/GetGamesList.php?name=" + name));
}
private async void GetGamesListRequestCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
GetGamesListItems.Clear();
var feedXml = XDocument.Parse(e.Result);
var gameDataTasks = feedXml.Root.Descendants("Game").Select(
async x => new GetGamesList
{
ID = (int)x.Element("id"),
GameTitle = (string)x.Element("GameTitle"),
ReleaseDate = (string)x.Element("ReleaseDate") ?? "N/A",
Platform = (string)x.Element("Platform") ?? "N/A",
BoxArtFrontThumb = new Uri(await GetBoxArtFrontThumbAsync((int)x.Element("id")), UriKind.RelativeOrAbsolute),
}).ToList();
var gameData = await Task.WhenAll(gameDataTasks);
foreach (var item in gameData)
{
GetGamesListItems.Add(item);
}
}
}
Below is where its finding and storing the images for the games.
public async Task<string> GetBoxArtFrontThumbAsync(int id)
{
var client = new HttpClient();
var result = await client.GetStringAsync("http://thegamesdb.net/api/GetArt.php?id=" + id);
var feedXml = XDocument.Parse(result);
var gameData = feedXml.Root.Descendants("Images").Select(x => new GetArt
{
BoxArtFrontThumb = new Uri(GetBoxArtFrontThumb(x), UriKind.RelativeOrAbsolute),
}).ToList();
return gameData.Single().BoxArtFrontThumb.ToString();
}
private static string GetBoxArtFrontThumb(XElement gameNode)
{
string data = "http://thegamesdb.net/banners/" + (string)gameNode.Descendants("boxart")
.FirstOrDefault(b => (string)b.Attribute("side") == "front");
if (data == "http://thegamesdb.net/banners/")
{
data = "/NoImage.jpg";
}
return data;
}
I really hope i explained this well enough and hope that there is someone that can help me solve this problem. Thanks.
Although you are using JumpList, the mechanism for Virtualizing the data is the same as the DataBoundListBox. (You can find more information here in the DataBoundListBox docs. There is a good tutorial using an OData service.)
In order for the Automatic mode to work properly, you need to be using Telerik's VirtualizingDataCollection object and initialize it with the proper arguments (count and page size).
I don't see this in the code you have provided above, can you please open a support ticket so that I can investigate further? See my comment above for the link. Let me know the ticket number and I'll provide further assistance.
I have a XAML page with this body:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
</Grid>
Now I want to add following controls in the Code Behind into the ContentPanel
<ViewportControl x:Name="viewport" ManipulationStarted="OnManipulationStarted" ManipulationDelta="OnManipulationDelta" ManipulationCompleted="OnManipulationCompleted" ViewportChanged="viewport_ViewportChanged">
<Canvas x:Name="canvas">
<Image x:Name="TestImage" RenderTransformOrigin="0,0" CacheMode="BitmapCache" ImageOpened="OnImageOpened">
<Image.RenderTransform>
<ScaleTransform x:Name="xform"/>
</Image.RenderTransform>
</Image>
</Canvas>
</ViewportControl>
Is there a way to do this by code?
Since
Creating the ViewportControl is OK. Creating the Canvas is OK. But creating the Image and it's "transform-stuff" - here I'm unable to code this
and you don't need to register names you can try this to create Image:
var img = new Image
{
RenderTransformOrigin = new Point(0,0),
CacheMode = new BitmapCache(),
RenderTransform = new ScaleTransform()
};
img.ImageOpened += OnImageOpened;
//and you add it to Canvas
Canvas canvas = new Canvas();
canvas.Children.Add(img);