Ways to address WPF Touch Screen Sensitivity - c#

I am trying to address issues with the sensitivity of a capacitive touch screen where WPF buttons are being triggered if the users fingers pass too close to the surface of the screen.
This issue is that many users end up with fingers or parts of their hands, other than their primary touch finger, close to the surface of the screen and this causes incorrect buttons to be triggered.
Adjusting the sensitivity of the screen seems to make little difference to I thought I could try modifying the button pressed events to only trigger a Click if the button is pressed for more than a certain amount of time.
Can anyone explain how I might create a custom button that would have an adjustable 'pressed' time before triggering a Clicked event.
If possible perhaps you would be kind enough to include a very simple C#/WPF application with such a custom button.
EDIT
OK, so I have created a subclassed Button using the code below, as per #kidshaw's answer but I think I must be missing a few things because nothing is getting called except the default Click event.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace AppName
{
public class TouchButton : Button
{
DoubleAnimationUsingKeyFrames _animation;
public static readonly DependencyProperty DelayElapsedProperty =
DependencyProperty.Register("DelayElapsed", typeof(double), typeof(TouchButton), new PropertyMetadata(0d));
public static readonly DependencyProperty DelayMillisecondsProperty =
DependencyProperty.Register("DelayMilliseconds", typeof(int), typeof(TouchButton), new PropertyMetadata(100));
public double DelayElapsed
{
get { return (double)this.GetValue(DelayElapsedProperty); }
set { this.SetValue(DelayElapsedProperty, value); }
}
public int DelayMilliseconds
{
get { return (int)this.GetValue(DelayMillisecondsProperty); }
set { this.SetValue(DelayMillisecondsProperty, value); }
}
private void BeginDelay()
{
this._animation = new DoubleAnimationUsingKeyFrames() { FillBehavior = FillBehavior.Stop };
this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(this.DelayMilliseconds)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
this._animation.Completed += (o, e) =>
{
this.DelayElapsed = 0d;
//this.Command.Execute(this.CommandParameter); // Replace with whatever action you want to perform
Console.Beep();
this.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
};
this.BeginAnimation(DelayElapsedProperty, this._animation);
}
private void CancelDelay()
{
// Cancel animation
this.BeginAnimation(DelayElapsedProperty, null);
}
private void TouchButton_TouchDown(object sender, System.Windows.Input.TouchEventArgs e)
{
this.BeginDelay();
}
private void TouchButton_TouchUp(object sender, System.Windows.Input.TouchEventArgs e)
{
this.CancelDelay();
}
}
}
How does the TouchButton_TouchDown method ever get called ? Don't I have to assign this to the TouchDown even handler somehow?
OK, I added a constructor and set the TouchDown/Up event handlers so that works but the CancelDelay() does not stop the event from being fired. It seems work OK and gets called when the user lift their finger but does not prevent the event from being triggered.

A time delay button would be the best option.
I have provided an example in this other stack overflow answer.
It uses an animation to delay triggering a command.
Hope it helps.
Do wpf have touch and gold gesture

You could almost certainly come up with a solution to do this. There are two approaches I would look at:
Create a specialisation derived from Button. You would override various handlers to implement your own behaviour.
Create an attached dependency property that subscribes to the preview mouse events. The preview events would allow you to intercept the up/down events to inject your own behaviour before the standard button handling can generate the click events.
Option #1 is probably the easiest to get your head around. The handling to generate click events lives in ButtonBase in both the OnMouseLeftButtonDown and OnMouseLeftButtonUp handlers. If you implement (override) your own version of both these handlers you should be able to fairly easily introduce a timer that only calls OnClick to generate the click event once a certain time has expired since the user pressed (and held down) the button.
PS: If you don't have it already, I highly recommend you get a copy of .NET Reflector. It will allow you to easily view the code for the WPF button implementation. I quickly used it to have a look at the WPF button implementation to get an idea of how it works in order to answer this question.

For completeness here is the full solution I used based on #kidshaw's original answer. Might save someone else some time fiddling around pulling the pieces together.
Note that I am getting VS Designer errors with it complaining about not finding the custom classes in the apps namespace. Strangely this does not seem to happen on an earlier version of VS so perhaps its a bug in VS.
TouchButton.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace TouchButtonApp
{
public class TouchButton : Button
{
DoubleAnimationUsingKeyFrames _animation;
bool _isCancelled = false;
public static readonly DependencyProperty DelayElapsedProperty =
DependencyProperty.Register("DelayElapsed", typeof(double), typeof(TouchButton), new PropertyMetadata(0d));
public static readonly DependencyProperty DelayMillisecondsProperty =
DependencyProperty.Register("DelayMilliseconds", typeof(int), typeof(TouchButton), new PropertyMetadata(Properties.Settings.Default.ButtonTouchDelay));
public static readonly DependencyProperty IsTouchedProperty =
DependencyProperty.Register("IsTouched", typeof(bool), typeof(TouchButton), new PropertyMetadata(false));
// Create a custom routed event by first registering a RoutedEventID
// This event uses the bubbling routing strategy
public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
"Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TouchButton));
public TouchButton()
{
this.TouchDown +=TouchButton_TouchDown;
this.TouchUp +=TouchButton_TouchUp;
}
// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
add { AddHandler(TapEvent, value); }
remove { RemoveHandler(TapEvent, value); }
}
// This method raises the Tap event
void RaiseTapEvent()
{
if (!_isCancelled)
{
//Console.Beep();
this.IsTouched = true;
Console.WriteLine("RaiseTapEvent");
RoutedEventArgs newEventArgs = new RoutedEventArgs(TouchButton.TapEvent);
RaiseEvent(newEventArgs);
}
}
public bool IsTouched
{
get { return (bool)this.GetValue(IsTouchedProperty); }
set { this.SetValue(IsTouchedProperty, value); }
}
public double DelayElapsed
{
get { return (double)this.GetValue(DelayElapsedProperty); }
set { this.SetValue(DelayElapsedProperty, value); }
}
public int DelayMilliseconds
{
get { return (int)this.GetValue(DelayMillisecondsProperty); }
set { this.SetValue(DelayMillisecondsProperty, value); }
}
//Start the animation and raise the event unless its cancelled
private void BeginDelay()
{
_isCancelled = false;
Console.WriteLine("BeginDelay ");
this._animation = new DoubleAnimationUsingKeyFrames() { FillBehavior = FillBehavior.Stop };
this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(this.DelayMilliseconds)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
this._animation.Completed += (o, e) =>
{
this.DelayElapsed = 0d;
//this.Command.Execute(this.CommandParameter); // Replace with whatever action you want to perform
RaiseTapEvent();
this.IsTouched = false;
};
this.BeginAnimation(DelayElapsedProperty, this._animation);
}
private void CancelDelay()
{
// Cancel animation
_isCancelled = true;
Console.WriteLine("CancelDelay ");
this.BeginAnimation(DelayElapsedProperty, null);
}
private void TouchButton_TouchDown(object sender, System.Windows.Input.TouchEventArgs e)
{
this.BeginDelay();
}
private void TouchButton_TouchUp(object sender, System.Windows.Input.TouchEventArgs e)
{
this.CancelDelay();
}
}
}
Custom animation when IsTouched event is triggered in App.xaml
<Style x:Key="characterKeyT" TargetType="{x:Type local:TouchButton}">
<Setter Property="Focusable" Value="False" />
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Margin" Value="6,4,8,4"/>
<Setter Property="FontSize" Value="24"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TouchButton}">
<Grid x:Name="grid">
<Border x:Name="border" CornerRadius="0">
<Border.Background>
<SolidColorBrush x:Name="BackgroundBrush" Color="{Binding Source={StaticResource settingsProvider}, Path=Default.ThemeColorPaleGray2}"/>
</Border.Background>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" TextElement.Foreground="Black"
TextElement.FontSize="24"></ContentPresenter>
</Border>
</Grid>
<ControlTemplate.Resources>
<Storyboard x:Key="FadeTimeLine" BeginTime="00:00:00.000" Duration="00:00:02.10">
<ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color"
To="#FF22B0E6"
Duration="00:00:00.10"/>
<ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color"
To="#FFECE8E8"
Duration="00:00:02.00"/>
</Storyboard>
</ControlTemplate.Resources>
<ControlTemplate.Triggers>
<Trigger Property="IsTouched" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource FadeTimeLine}"/>
</Trigger.EnterActions>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ThemeSolidColorBrushPaleGray}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Background" TargetName="border" Value="{StaticResource ThemeSolidColorBrushPaleGray2}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" TargetName="grid" Value="0.25"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
XAML Usage
<UserControl x:Class="TouchButtonApp.Keyboard1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TouchButtonApp"
mc:Ignorable="d"
d:DesignHeight="352" d:DesignWidth="1024">
<Grid>
<Grid Margin="0,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="90*"/>
<RowDefinition Height="90*"/>
<RowDefinition Height="90*"/>
<RowDefinition Height="90*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="93*"/>
<ColumnDefinition Width="93*"/>
<ColumnDefinition Width="93*"/>
<ColumnDefinition Width="93*"/>
<ColumnDefinition Width="93*"/>
<ColumnDefinition Width="93*"/>
<ColumnDefinition Width="93*"/>
<ColumnDefinition Width="93*"/>
<ColumnDefinition Width="93*"/>
<ColumnDefinition Width="93*"/>
<ColumnDefinition Width="93*"/>
</Grid.ColumnDefinitions>
<local:TouchButton x:Name="qButton" Tap="Button_Click" Content="Q" Grid.Row="1" Style="{DynamicResource characterKeyT}" />
<local:TouchButton x:Name="wButton" Tap="Button_Click" Content="W" Grid.Column="1" Grid.Row="1" Style="{DynamicResource characterKeyT}" />
...

Related

WPF window covers taskbar

I'm creating a custom WPF window with, WindowStyle=None, AllowsTransparency=True, and ResizeMode = CanMinimize.
I have two events(That I created to understand events in WPF), PreviewMouseDoubleClick and PreviewMouseMove.
Here's the XAML:
<Style TargetType="{x:Type local:CustomWindow}">
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="ResizeMode" Value="CanMinimize"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomWindow}">
<Border BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}">
<Grid Background="{TemplateBinding GridBackground}">
<ContentPresenter/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's the Code Behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace CustomWindows
{
public class CustomWindow : Window
{
/* Dependency properties */
public Brush GridBackground
{
get { return (Brush)GetValue(GridBackgroundProperty); }
set { SetValue(GridBackgroundProperty, value); }
}
// Using a DependencyProperty as the backing store for GridBackground. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GridBackgroundProperty =
DependencyProperty.Register("GridBackground", typeof(Brush), typeof(Window), new PropertyMetadata(null));
/* Constructors */
static CustomWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomWindow), new FrameworkPropertyMetadata(typeof(CustomWindow)));
}
/* overriders */
public override void OnApplyTemplate()
{
PreviewMouseDoubleClick += (s, e) =>
{
WindowState = (WindowState == WindowState.Maximized) ? WindowState.Normal : WindowState.Maximized;
};
PreviewMouseMove += (s, e) =>
{
if(Mouse.LeftButton == MouseButtonState.Pressed)
{
DragMove();
}
};
base.OnApplyTemplate();
}
}
}
On Dragging this window, it covers the taskbar. I am not sure as to why.
Here's the link of the image that describes the behavior I'm talking about:
http://imgur.com/ba3ADoL Issue Description Image
Also on restoring the window, if the mouse pointer is out of bounds of the window, the focus is still not given to Desktop. If the mouse is manually moved a bit later. Focus is given to Desktop. This behavior seems strange to me. Any help will be greatly appreciated.
Thanks in Advance.
I would rather use WindowChrome class in an answer by dss539 at: How to create custom window chrome in wpf?
.NET 4.5 added a new class that greatly simplifies this.
The WindowChrome class enables you to extend Windows Presentation Foundation (WPF) content into the non-client area of a window that is typically reserved for the operating system’s window manager.
You can find a tutorial here.
And here's a short example usage.

Styles and bindings acting oddly

Right. I've got a small program (that replicates my issue). Basically, it tries to bind to some properties of the object it's styling. It kind of works: it gives me the default value (from the dependency property). I've been thinking this may be because the Style's RelativeSource Self isn't the same as the TextBox it's styling's one. But I don't know. I've tried debugging this, checking time and again that the value set in XAML was actually set. The thing is, with a smaller test program it works. This is just a scale up from that. I don't know what's going wrong.
Thanks!
The code for reproducing this issue:
MainWindow.xaml
<Window x:Class="MyNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace"
Title="My title." Height="350" Width="425" MaxHeight="350" MaxWidth="425" MinHeight="350" MinWidth="425">
<Window.Resources>
<ResourceDictionary Source="TestDictionary.xaml"/>
</Window.Resources>
<Grid>
<TextBox Style="{StaticResource TextBoxWithDefault}" FontSize="36" lcl:MyOptions.Default="Not default." VerticalAlignment="Center"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace MyNamespace
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public static class MyOptions
{
public static string GetDefault(DependencyObject obj)
{
return (string)obj.GetValue(DefaultProperty);
}
public static void SetDefault(DependencyObject obj, string value)
{
obj.SetValue(DefaultProperty, value);
}
public static readonly DependencyProperty DefaultProperty =
DependencyProperty.RegisterAttached(
"Default",
typeof(string),
typeof(MyOptions),
new PropertyMetadata("Default"));
}
}
TestDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style TargetType="TextBox" x:Key="TextBoxWithDefault">
<Style.Resources>
<Label Content="{Binding Path=(lcl:MyOptions.Default), Mode=TwoWay, RelativeSource={RelativeSource Self}}"
Foreground="LightGray"
FontSize="{Binding Path=(FontSize), Mode=TwoWay, RelativeSource={RelativeSource Self}}" x:Key="TheLabel"/>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background">
<Setter.Value>
<VisualBrush AlignmentX="Left" AlignmentY="Center" Stretch="None" Visual="{DynamicResource TheLabel}"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background">
<Setter.Value>
<VisualBrush AlignmentX="Left" AlignmentY="Center" Stretch="None" Visual="{DynamicResource TheLabel}"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
I have no idea what's going wrong here, as a scaled down version of this works perfectly. There's probably something I overlooked, that will seem pretty obvious when I find it. But I can't find it now.
EDIT: Well, it seems I was dumb. The original version (here) uses a Trigger, which means that it gets the parent textbox's value. The question now is: how can I get it working?
Thanks for your time!
The real show-stopper here is that when you use the Label in a VisualBrush, the label isn't part of the TextBox' "Visual Tree" (see for example Sheldon Xiao's answer to this similar question on MSDN: Binding Problem inside VisualBrush).
This means that the label won't inherit the text box' DataContext, and you can't reach the text box from a RelativeSource binding either. In contrast, the accepted answer in your other post sets the actual content of a button, which does make the content part of the button's visual tree.
So I don't think there's a pure XAML solution to this problem - pushing the correct MyOptions.Default from the text box to the label. One possible code-based solution is to scrap the TextBoxWithDefault style and do everything from your attached property when Default changes:
...
public static readonly DependencyProperty DefaultProperty =
DependencyProperty.RegisterAttached(
"Default",
typeof(string),
typeof(MyOptions),
//Listen for changes in "Default":
new PropertyMetadata(null, OnMyDefaultChanged));
private static void OnMyDefaultChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var text = (TextBox)sender;
var myDefault = e.NewValue;
var defaultLabel = new Label();
defaultLabel.Foreground = Brushes.LightGray;
//Explicitly bind the needed value from the TextBox:
defaultLabel.SetBinding(Label.ContentProperty,
new Binding()
{
Source = text,
Path = new PropertyPath(MyOptions.DefaultProperty)
});
text.Background = new VisualBrush()
{
Visual = defaultLabel,
AlignmentX = AlignmentX.Left,
AlignmentY = AlignmentY.Center,
Stretch = Stretch.None
};
text.TextChanged += new TextChangedEventHandler(OnTextWithDefaultChanged);
}
private static void OnTextWithDefaultChanged(object sender,
TextChangedEventArgs e)
{
var text = (TextBox)sender;
var defaultLabel = (text.Background as VisualBrush).Visual as Label;
defaultLabel.Visibility = string.IsNullOrEmpty(text.Text) ?
Visibility.Visible :
Visibility.Collapsed;
}

how can i add lots of images to listbox

I am making this app for windows phone 7, what I do is retrieve all the images from camera roll, saved pictures and other folder and display them in the listbox inside a wrap panel so they are displayed side by side....the thumbnail of the images is actually displayed hear.....
but as the number of images are increasing UI gets very slow and scrolling takes time...
I read many post and other question I think data virtualization or lazy loading is what I need but I am not understanding how can I use it, I saw the post from shawn oster and peter torr.....
I use a backgroundworker to load the images...
here's how...
void backroungWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Dispatcher.BeginInvoke(() =>
{
foreach (string fileName in fileStorage.GetFileNames("images//*.*"))
{
if (fileName == null)
break;
string filepath = System.IO.Path.Combine("images", fileName);
try
{
using (IsolatedStorageFileStream imageStream = fileStorage.OpenFile(filepath, FileMode.Open))
{
var imageSource = PictureDecoder.DecodeJpeg(imageStream);
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(imageStream);
var item = new ImageToName { bmp = bitmapImage, FileName = fileName };
vltBitmapImage.Add(item);
imageStream.Dispose();
imageStream.Close();
}
}
catch
{
Exception x = new Exception();
}
}
if (vltBitmapImage.Count() != 0)
{
lone.Visibility = Visibility.Collapsed;
this.vaultbox.ItemsSource = vltBitmapImage;
}
else
lone.Visibility = Visibility.Visible;
});
}
any help is greatly appreciated.....
sorry for being a noob...
Try this sample from code project, it explain how it work and comes with a full sample project
See: Loading Data when the User scrolls to the end of the list
If you want to add a Lazy load to a listbox you must setup a listner for your list box and change the way you load data into your data model, so first lest set up at the XAML code for a listbox:
In your page resource add this style, and note the loaded event its include ("ScrollViewer_Loaded"):
...
<phone:PhoneApplicationPage.Resources>
<Style x:Key="BusinessListBoxStyle"
TargetType="ListBox">
<Setter Property="Background"
Value="Transparent" />
<Setter Property="Foreground"
Value="{StaticResource PhoneForegroundBrush}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility"
Value="Auto" />
<Setter Property="BorderThickness"
Value="0" />
<Setter Property="BorderBrush"
Value="Transparent" />
<Setter Property="Padding"
Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<ScrollViewer x:Name="scrollViewer"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Padding="{TemplateBinding Padding}"
Loaded="ScrollViewer_Loaded">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</phone:PhoneApplicationPage.Resources>
...
The add a reference to your style for the listbox, and bind your itemsSource to a list of items from your viewModel.
...
<ListBox x:Name="myList"
ItemsSource="{Binding myDataSource}"
Style="{StaticResource BusinessListBoxStyle}">
...
Next you have to set some code that loads data into the datamodel, when you reach the end of the list element in the list (the datamodel start loading more data into the mode, adds more items) Lazyloading!!
The way i normaly do this is by listing to the vertical offset of the listbox's scrollbar, and if its is about 1/4 from the edge i starts loading more items into the datamodel.
In the ScrollViewer loaded handler i set up at VertialOffset listener, by using the DependencyProperty, see code below:
public static readonly DependencyProperty ListVerticalOffsetProperty =
DependencyProperty.Register(
"ListVerticalOffset",
typeof(double),
typeof(MyPage),
new PropertyMetadata(new PropertyChangedCallback(OnListVerticalOffsetChanged))
);
private ScrollViewer _listScrollViewer;
private void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
_listScrollViewer = sender as ScrollViewer;
Binding binding = new Binding();
binding.Source = _listScrollViewer;
binding.Path = new PropertyPath("VerticalOffset");
binding.Mode = BindingMode.OneWay;
this.SetBinding(ListVerticalOffsetProperty, binding);
}
public double ListVerticalOffset
{
get { return (double)this.GetValue(ListVerticalOffsetProperty); }
set { this.SetValue(ListVerticalOffsetProperty, value); }
}
private double _lastFetch;
private static void OnListVerticalOffsetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
MyPage page = obj as MyPage;
ScrollViewer viewer = page._listScrollViewer;
if (viewer != null)
{
if (page._lastFetch < viewer.ScrollableHeight)
{
// Trigger within 1/4 the viewport.
if (viewer.VerticalOffset >= (viewer.ScrollableHeight - (viewer.ViewportHeight / 4)))
{
page._lastFetch = viewer.ScrollableHeight;
MyViewModel _tmpviewmodel = page.DataContext as MyViewModel;
if ((_tmpviewmodel != null) && (_tmpviewmodel.HasMoreItems))
_tmpviewmodel.GetMoreItems();
}
}
}
}
Note here i make use of a MyViewModel, that holds all the items the listbox i binded to, and has methods for load items from a database, the isolatedstore, the web or what ever your needs are.
You just have to find your way of only load a part of your data into the viewmodel. I your case i will first load a list of all the files you need to load (that is just to retreive the list from GetFileNames in the IsoLatedStore). Then mayby only loads 20 pics at the time!

Show Ellipsis(...) Button When Text Exceeds Range WPF

I have one TextBlock having width say 100. When the text length is a large one I want to show the characters that is accomodated in that textblock and a (...) button besides the text to specify user that more text is also there. Upon click on that (...) button, the full text will be shown in a separate pop up window.
So i want how the dynamic (...) button will be shown whenever the text length exceed the size of the textblock. Please answer
This isn't exactly what you want, but it's a similar idea and just uses the baked-in stuff:
<TextBlock MaxWidth="200"
Text="{Binding YourLongText}"
TextTrimming="WordEllipsis"
ToolTip="{Binding YourLongText}" />
So you have a TextBlock with a maximum width, and when the text can't fit it displays an ellipsis ("..."). Hovering over the TextBlock with your mouse will show the full text in a ToolTip.
Just experience the same requirement for adding ellipsis on button so adding the solution here
<Style x:Key="editButton" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center" >
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"></Setter>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Transparent"/>
</Trigger>
</Style.Triggers>
</Style>
Notice the resources in content presenter.
I believe what you want is to set the TextTrimming property. Settng it to WordElilipsis or CharacterEllipsis should provide what you need.
My solution to the problem is probably overkill, but allows for some configuration and control.
I created a behavior that allows me to set the character limit for each binding.
internal class EllipsisStringBehavior
{
public static readonly DependencyProperty CharacterLimitDependencyProperty = DependencyProperty.RegisterAttached("CharacterLimit", typeof(int), typeof(EllipsisStringBehavior), new PropertyMetadata(255, null, OnCoerceCharacterLimit));
public static readonly DependencyProperty InputTextDependencyProperty = DependencyProperty.RegisterAttached("InputText", typeof(string), typeof(EllipsisStringBehavior), new PropertyMetadata(string.Empty, OnInputTextChanged));
// Input Text
public static string GetInputText(DependencyObject dependencyObject)
{
return Convert.ToString(dependencyObject.GetValue(InputTextDependencyProperty));
}
public static void SetInputText(DependencyObject dependencyObject, string inputText)
{
dependencyObject.SetValue(InputTextDependencyProperty, inputText);
}
// Character Limit
public static int GetCharacterLimit(DependencyObject dependencyObject)
{
return Convert.ToInt32(dependencyObject.GetValue(CharacterLimitDependencyProperty));
}
public static void SetCharacterLimit(DependencyObject dependencyObject, object characterLimit)
{
dependencyObject.SetValue(CharacterLimitDependencyProperty, characterLimit);
}
private static void OnInputTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBlock textblock = (TextBlock)d;
string input = e.NewValue == null ? string.Empty : e.NewValue.ToString();
int limit = GetCharacterLimit(d);
string result = input;
if (input.Length > limit && input.Length != 0)
{
result = $"{input.Substring(0, limit)}...";
}
textblock.Text = result;
}
private static object OnCoerceCharacterLimit(DependencyObject d, object baseValue)
{
return baseValue;
}
}
I then simply add the using to my user control...
<UserControl
xmlns:behavior="clr-namespace:My_APP.Helper.Behavior"
d:DesignHeight="300" d:DesignWidth="300">
...and apply the behavior to the TextBlock control I wish to use it on.
<TextBlock Margin="0,8,0,8"
behavior:EllipsisStringBehavior.CharacterLimit="10"
behavior:EllipsisStringBehavior.InputText="{Binding Path=DataContext.FeedItemTwo.Body, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignSubheadingTextBlock}"
FontSize="14"/>
Hope this helps.

How to Access a Button present inside a Custom Control, from the implementing page?

I have my generic.xaml containing the following code:
<ControlTemplate TargetType="local:customVideoControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="600"/>
<RowDefinition Height="200"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<MediaElement x:Name="customMediaPlayer" Source="{TemplateBinding CustomMediaSource}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
Grid.Row="0" Grid.ColumnSpan="3"
/>
<ToggleButton x:Name="playPauseBtn" Height="50" Width="50" Content="Pause" Grid.Row="1" Grid.Column="0"/>
<Button x:Name="prevBtn" Height="50" Width="50" Content="Prev" Grid.Row="1" Grid.Column="1"/>
<Button x:Name="nextBtn" Height="50" Width="50" Content="Next" Grid.Row="1" Grid.Column="2"/>
</Grid>
</ControlTemplate>
Now on applyTemplate , I am accessing the controls like below:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ToggleButton playPauseBtn = GetTemplateChild("playPauseBtn") as ToggleButton;
Button prevBtn= GetTemplateChild("prevBtn") as Button;
Button nextBtn = GetTemplateChild("nextBtn") as Button;
MediaElement customMediaPlayer = GetTemplateChild("customMediaPlayer") as MediaElement;
playPauseBtn.Checked += (obj, Args) =>
{
customMediaPlayer.Pause();
playPauseBtn.Content = "Play";
};
playPauseBtn.Unchecked += (obj, Args) =>
{
customMediaPlayer.Play();
playPauseBtn.Content = "Pause";
};
nextBtn.Click += (obj, Args) =>
{
customMediaPlayer.Source=new Uri(CustomMediaSource.ToString(),UriKind.RelativeOrAbsolute);
};
prevBtn.Click += (obj, Args) =>
{
customMediaPlayer.Source = new Uri(CustomMediaSource.ToString(), UriKind.RelativeOrAbsolute);
};
}
Now I want acccess the nextBtn, in the page where I am implementing like
CustomVideoControl myVControl=new CustomVideoControl();
This will create the instance of the control, but I want to do something on the click of
next and previous button, thta is present inside the CustomVideoControl in generic.xaml. Any help will be greatly appreciated.
Thanks,
Subhen
You just need to add a couple of events to your Control.
public event EventHandler MovedPrevious
public event EventHandler MovedNext
Now this are typically implemented like this:-
protected virtual void OnMovedPrevious(EventArgs e)
{
var handler = MovedPrevious;
if (handler != null)
handler(this, e);
}
protected virtual void OnMovedNext(EventArgs e)
{
var handler = MovedNext;
if (handler != null)
handler(this, e);
}
Now in your existing click events:-
nextBtn.Click += (obj, Args) =>
{
customMediaPlayer.Source=new Uri(CustomMediaSource.ToString(),UriKind.RelativeOrAbsolute); //No idea what this doing
OnMovedNext(EventArgs.Empty);
};
prevBtn.Click += (obj, Args) =>
{
customMediaPlayer.Source = new Uri(CustomMediaSource.ToString(), UriKind.RelativeOrAbsolute); //No idea what this is doing either
OnMovedPrevious(EventArgs.Empty);
};
Now in your consuming code you can do this sort of thing:-
CustomVideoControl myVControl=new CustomVideoControl();
myVControl.MovedNext += (s, args) => { /* deal with next */ };
myVControl.MovedPrevious += (s, args) => { /* deal with previous */ };
Sorry, but you're doing it wrong.
There's no good reason why you should have a reference to elements inside a DataTemplate IMO.
[...Read more at this forum post...]
You could create public events in your custom control, something like NextButtonClicked and PreviousButtonClicked.
I have a feeling, that you're trying to mimic EventSetter behaviour. If I'm right, please just take a look on this simple example:
<Style TargetType="{x:Type Button}" x:Key="SomeID">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Cursor" Value="Hand"></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
</Trigger>
</Style.Triggers>
<EventSetter Event="MouseUp" Handler="DoSomething_Click"></EventSetter>
</Style>
This code assigns your custom event to some text block's regular action directly from XAML (you don't have to pollute your code behind with accessing controls' properties).
I hope this is helpful, but if not, please give me a shout.
Edit:
Sorry for not being perfectly clear (this was just a quickly pasted code snippet). Please have a look on a complete example:
Styles for your next/previous buttons:
<Style TargetType="{x:Type Button}" x:Key="PreviousButtonstyle">
<EventSetter Event="Click" Handler="OnMovedPrevious"></EventSetter>
</Style>
<Style TargetType="{x:Type Button}" x:Key="NextButtonstyle">
<EventSetter Event="Click" Handler="OnMovedNext"></EventSetter>
</Style>
Code behind:
public event EventHandler MovedPrevious;
public event EventHandler MovedNext;
protected void OnMovedPrevious(object sender, RoutedEventArgs e)
{
if (MovedPrevious != null)
{
MovedPrevious(this, e);
}
}
protected void OnMovedNext(object sender, RoutedEventArgs e)
{
if (MovedNext != null)
{
MovedNext(this, e);
}
}
Since now on you can access OnMovedNext and OnMovedPrevious directly from your control's handling conrol/whatever just as Anthony posted.
Sorry if my previous answer was confusing, but it supposed to be just an inspiration what to do :)
Edit:
I haven't noticed that this regards only Silverlight for which I apologize :) But, it works perfectly for WPF if you wish to try.

Categories

Resources