I have a StackPanel in my WinRT C# Metro app that I want to use as container for a label/value pair (TextBlock and TextBox) like this:
<StackPanel Orientation="Horizontal" Width="400">
<TextBlock Text="Label" Width="150" />
<TextBox Text="Value" />
</StackPanel>
Now what I want is to let the TextBox automatically fill the remaining horizontal space of the StackPanel. Is this possible in any way? HorizontalAlignment/HorizontalContentAlignment don't work.
I know that an alternative would be to define a Grid for that. The problem is that I have this construct several times and want to use it in a Style definition. I don't want to define the Grid's definition with rows and columns x times...
Perhaps the alternative would be to define a custom user control, but I hoped there would be an easy possibility to get the TextBox to stretch.
Unfortunately the alternative (DockPanel) isnt available for Metro. You could try a WrapGrid, but I dont know if it'll solve your problem (Ive never used it).
The only real way of doing this is using a Grid as you described:
<Grid Width="400">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Label" Width="150" />
<TextBox Text="Value" Grid.Column="1" />
</Grid>
I am not sure which element you put HorizontalAlignment to, but this is what works for me:
<StackPanel>
<TextBlock Text="Title" Style="..."/>
<TextBlock Margin="0,0,0,10" Text="Description" Style="..."/>
<TextBox HorizontalAlignment="Stretch" />
<Button Margin="0" Content="Save"/>
</StackPanel>
This stretches the TextBox to the whole width of the StackPanel.
Came across this question while looking up whether a WinRT DockPanel existed.
I ended up slightly modifying the DockPanel that comes with the Silverlight Toolkit, and it seems to work.
To use, add xmlns:toolkit="using:System.Windows.Controls" to the top, and taking the question above as the scenario, we could write:
<toolkit:DockPanel Width="400">
<TextBlock Text="Label" Width="150" toolkit:DockPanel.Dock="Left" />
<TextBox Text="Value" />
</toolkit:DockPanel>
DockPanel.cs:
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
// Modified slightly for Windows Runtime support.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace System.Windows.Controls
{
public enum Dock
{
// Summary:
// Specifies that the control should be positioned on the left of the control.
Left = 0,
//
// Summary:
// Specifies that the control should be positioned on top of the control.
Top = 1,
//
// Summary:
// Specifies that the control should be positioned on the right of the control.
Right = 2,
//
// Summary:
// Specifies that the control should be positioned at the bottom of the control.
Bottom = 3,
}
/// <summary>
/// Arranges child elements around the edges of the panel. Optionally,
/// last added child element can occupy the remaining space.
/// </summary>
/// <QualityBand>Stable</QualityBand>
public class DockPanel : Panel
{
/// <summary>
/// A value indicating whether a dependency property change handler
/// should ignore the next change notification. This is used to reset
/// the value of properties without performing any of the actions in
/// their change handlers.
/// </summary>
private static bool _ignorePropertyChange;
#region public bool LastChildFill
/// <summary>
/// Gets or sets a value indicating whether the last child element
/// added to a <see cref="T:System.Windows.Controls.DockPanel" />
/// resizes to fill the remaining space.
/// </summary>
/// <value>
/// True if the last element added resizes to fill the remaining space,
/// false to indicate the last element does not resize. The default is
/// true.
/// </value>
public bool LastChildFill
{
get { return (bool)GetValue(LastChildFillProperty); }
set { SetValue(LastChildFillProperty, value); }
}
/// <summary>
/// Identifies the
/// <see cref="P:System.Windows.Controls.DockPanel.LastChildFill" />
/// dependency property.
/// </summary>
public static readonly DependencyProperty LastChildFillProperty =
DependencyProperty.Register(
"LastChildFill",
typeof(bool),
typeof(DockPanel),
new PropertyMetadata(true, OnLastChildFillPropertyChanged));
/// <summary>
/// LastChildFillProperty property changed handler.
/// </summary>
/// <param name="d">DockPanel that changed its LastChildFill.</param>
/// <param name="e">Event arguments.</param>
private static void OnLastChildFillPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DockPanel source = d as DockPanel;
source.InvalidateArrange();
}
#endregion public bool LastChildFill
#region public attached Dock Dock
/// <summary>
/// Gets the value of the
/// <see cref="P:System.Windows.Controls.DockPanel.Dock" /> attached
/// property for the specified element.
/// </summary>
/// <param name="element">
/// The element from which the property value is read.
/// </param>
/// <returns>
/// The <see cref="P:System.Windows.Controls.DockPanel.Dock" /> property
/// value for the element.
/// </returns>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "DockPanel only has UIElement children")]
public static Dock GetDock(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (Dock)element.GetValue(DockProperty);
}
/// <summary>
/// Sets the value of the
/// <see cref="P:System.Windows.Controls.DockPanel.Dock" /> attached
/// property for the specified element to the specified dock value.
/// </summary>
/// <param name="element">
/// The element to which the attached property is assigned.
/// </param>
/// <param name="dock">
/// The dock value to assign to the specified element.
/// </param>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "DockPanel only has UIElement children")]
public static void SetDock(UIElement element, Dock dock)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(DockProperty, dock);
}
/// <summary>
/// Identifies the
/// <see cref="P:System.Windows.Controls.DockPanel.Dock" />
/// attached property.
/// </summary>
public static readonly DependencyProperty DockProperty =
DependencyProperty.RegisterAttached(
"Dock",
typeof(Dock),
typeof(DockPanel),
new PropertyMetadata(Dock.Left, OnDockPropertyChanged));
/// <summary>
/// DockProperty property changed handler.
/// </summary>
/// <param name="d">UIElement that changed its Dock.</param>
/// <param name="e">Event arguments.</param>
[SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Almost always set from the attached property CLR setter.")]
private static void OnDockPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Ignore the change if requested
if (_ignorePropertyChange)
{
_ignorePropertyChange = false;
return;
}
UIElement element = (UIElement)d;
Dock value = (Dock)e.NewValue;
// Validate the Dock property
if ((value != Dock.Left) &&
(value != Dock.Top) &&
(value != Dock.Right) &&
(value != Dock.Bottom))
{
// Reset the property to its original state before throwing
_ignorePropertyChange = true;
element.SetValue(DockProperty, (Dock)e.OldValue);
string message = string.Format(
CultureInfo.InvariantCulture,
"InvalidValue",
value);
throw new ArgumentException(message, "value");
}
// Cause the DockPanel to update its layout when a child changes
DockPanel panel = VisualTreeHelper.GetParent(element) as DockPanel;
if (panel != null)
{
panel.InvalidateMeasure();
}
}
#endregion public attached Dock Dock
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:System.Windows.Controls.DockPanel" /> class.
/// </summary>
public DockPanel()
{
}
/// <summary>
/// Measures the child elements of a
/// <see cref="T:System.Windows.Controls.DockPanel" /> in preparation
/// for arranging them during the
/// <see cref="M:System.Windows.Controls.DockPanel.ArrangeOverride(System.Windows.Size)" />
/// pass.
/// </summary>
/// <param name="constraint">
/// The area available to the
/// <see cref="T:System.Windows.Controls.DockPanel" />.
/// </param>
/// <returns>
/// The desired size of the
/// <see cref="T:System.Windows.Controls.DockPanel" />.
/// </returns>
[SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")]
protected override Size MeasureOverride(Size constraint)
{
double usedWidth = 0.0;
double usedHeight = 0.0;
double maximumWidth = 0.0;
double maximumHeight = 0.0;
// Measure each of the Children
foreach (UIElement element in Children)
{
// Get the child's desired size
Size remainingSize = new Size(
Math.Max(0.0, constraint.Width - usedWidth),
Math.Max(0.0, constraint.Height - usedHeight));
element.Measure(remainingSize);
Size desiredSize = element.DesiredSize;
// Decrease the remaining space for the rest of the children
switch (GetDock(element))
{
case Dock.Left:
case Dock.Right:
maximumHeight = Math.Max(maximumHeight, usedHeight + desiredSize.Height);
usedWidth += desiredSize.Width;
break;
case Dock.Top:
case Dock.Bottom:
maximumWidth = Math.Max(maximumWidth, usedWidth + desiredSize.Width);
usedHeight += desiredSize.Height;
break;
}
}
maximumWidth = Math.Max(maximumWidth, usedWidth);
maximumHeight = Math.Max(maximumHeight, usedHeight);
return new Size(maximumWidth, maximumHeight);
}
/// <summary>
/// Arranges the child elements of the
/// <see cref="T:System.Windows.Controls.DockPanel" /> control.
/// </summary>
/// <param name="arrangeSize">
/// The area in the parent element that the
/// <see cref="T:System.Windows.Controls.DockPanel" /> should use to
/// arrange its child elements.
/// </param>
/// <returns>
/// The actual size of the
/// <see cref="T:System.Windows.Controls.DockPanel" /> after the child
/// elements are arranged. The actual size should always equal
/// <paramref name="arrangeSize" />.
/// </returns>
[SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")]
protected override Size ArrangeOverride(Size arrangeSize)
{
double left = 0.0;
double top = 0.0;
double right = 0.0;
double bottom = 0.0;
// Arrange each of the Children
UIElementCollection children = Children;
int dockedCount = children.Count - (LastChildFill ? 1 : 0);
int index = 0;
foreach (UIElement element in children)
{
// Determine the remaining space left to arrange the element
Rect remainingRect = new Rect(
left,
top,
Math.Max(0.0, arrangeSize.Width - left - right),
Math.Max(0.0, arrangeSize.Height - top - bottom));
// Trim the remaining Rect to the docked size of the element
// (unless the element should fill the remaining space because
// of LastChildFill)
if (index < dockedCount)
{
Size desiredSize = element.DesiredSize;
switch (GetDock(element))
{
case Dock.Left:
left += desiredSize.Width;
remainingRect.Width = desiredSize.Width;
break;
case Dock.Top:
top += desiredSize.Height;
remainingRect.Height = desiredSize.Height;
break;
case Dock.Right:
right += desiredSize.Width;
remainingRect.X = Math.Max(0.0, arrangeSize.Width - right);
remainingRect.Width = desiredSize.Width;
break;
case Dock.Bottom:
bottom += desiredSize.Height;
remainingRect.Y = Math.Max(0.0, arrangeSize.Height - bottom);
remainingRect.Height = desiredSize.Height;
break;
}
}
element.Arrange(remainingRect);
index++;
}
return arrangeSize;
}
}
}
How about using a RelativePanel.
<RelativePanel Width="320" Height="180" BorderBrush="Black" BorderThickness="1">
<TextBox RelativePanel.AlignTopWithPanel="True"
RelativePanel.AlignBottomWithPanel="True"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
TextWrapping="Wrap"/>
</RelativePanel>
figured out that the best native control to neast the contents is the grid look at my exmple that worked fine to me:
<Grid >
<TextBox x:Name="txtCountry" Margin="0,8,41,8" />
<Button x:Name="btnGetCountry" Content="..." Margin="0,8,0,8" HorizontalAlignment="Right" Click="btnGetCountry_Click"> </Button>
</Grid>
this will generate a nice adaptable layout with a textbox steching and a butoon on it side that maintains his aspect
Related
Imagine the following XAML:
<Page
x:Class="MyProject.MainPage"
xmlns:local="using:MyProject">
<local:MyStackPanel>
<Button Content="Go" Click="OnGo"/>
</local:MyStackPanel>
</Page>
XAML assumes that OnGois a method in the page class. Can I somehow tell it declaratively that OnGo resides in MyStackPanel instead?
I know I can assign the event handler programmatically once the page is loaded. I'm wondering if it's possible by purely XAML means. "No" is an acceptable answer :)
For your requirement, you could bind the MyStackPanel DataContext with itself. Then create BtnClickCommand that used to bind button command in the MyStackPanel class like following.
public class MyStackPanel : StackPanel
{
public MyStackPanel()
{
}
public ICommand BtnClickCommand
{
get
{
return new RelayCommand(() =>
{
});
}
}
}
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
/// <summary>
/// Raised when RaiseCanExecuteChanged is called.
/// </summary>
public event EventHandler CanExecuteChanged;
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
/// <summary>
/// Determines whether this RelayCommand can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
/// <returns>true if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
/// <summary>
/// Executes the RelayCommand on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
public void Execute(object parameter)
{
_execute();
}
/// <summary>
/// Method used to raise the CanExecuteChanged event
/// to indicate that the return value of the CanExecute
/// method has changed.
/// </summary>
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
Usage
<local:MyStackPanel DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Button Command="{Binding BtnClickCommand}" Content="ClickMe" />
</local:MyStackPanel>
I would like to automatically highlight URL, Email and phone number in UWP. It is possible in Android but it seems this features has been forgotten by Microsoft.
In my use case, I get the text from a web service, so I don't know the text format which is a user text input on the web platform.
The platform is not supporting this feature (yet). When I've to do the same thing, I've ended with my own solution which is to:
create an attached property receiving the text to format
use some regex to extract the URL, phone numbers, email addresses from it
generate an Inlines collection which I'm injecting to the attached TextBlock control
The regex used are covering a lot of cases but some edge cases can be still missing.
It is used this way:
<TextBlock uwpext:TextBlock.InteractiveText="Here is a link www.bing.com to send to a#a.com or 0000000000" />
The attached property code:
// -------------------------------------------------------------------------------------------
/// <summary>
/// The regex to detect the URL from the text content
/// It comes from https://gist.github.com/gruber/249502 (http://daringfireball.net/2010/07/improved_regex_for_matching_urls)
/// </summary>
private static readonly Regex UrlRegex = new Regex(#"(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'"".,<>?«»“”‘’]))", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(500));
// -------------------------------------------------------------------------------------------
/// <summary>
/// The regex to detect the email addresses
/// It comes from https://msdn.microsoft.com/en-us/library/01escwtf.aspx
/// </summary>
private static readonly Regex EmailRegex = new Regex(#"(?("")("".+?(?<!\\)""#)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])#))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(500));
// -------------------------------------------------------------------------------------------
/// <summary>
/// The regex to detect the phone numbers from the raw message
/// </summary>
private static readonly Regex PhoneRegex = new Regex(#"\+?[\d\-\(\)\. ]{5,}", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
// -------------------------------------------------------------------------------------------
/// <summary>
/// The default prefix to use to convert a relative URI to an absolute URI
/// The Windows RunTime is only working with absolute URI
/// </summary>
private const string RelativeUriDefaultPrefix = "http://";
// -------------------------------------------------------------------------------------------
/// <summary>
/// The dependency property to generate an interactive text in a text block.
/// When setting this property, we will parse the value and transform the hyperlink or the email address to interactive fields that the user can interact width.
/// The raw text will be parsed and convert to a collection of inlines.
/// </summary>
public static readonly DependencyProperty InteractiveTextProperty = DependencyProperty.RegisterAttached("InteractiveText", typeof(string), typeof(TextBlock), new PropertyMetadata(null, OnInteractiveTextChanged));
// -------------------------------------------------------------------------------------------
/// <summary>
/// The event callback for the interactive text changed event
/// We will parse the raw text and generate the inlines that will wrap the interactive items (URL...)
/// </summary>
/// <param name="d">the object which has raised the event</param>
/// <param name="e">the change information</param>
private static void OnInteractiveTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as Windows.UI.Xaml.Controls.TextBlock;
if(textBlock == null) return;
// we remove all the inlines
textBlock.Inlines.Clear();
// if we have no data, we do not need to go further
var rawText = e.NewValue as string;
if(string.IsNullOrEmpty(rawText)) return;
var lastPosition = 0;
var matches = new Match[3];
do
{
matches[0] = UrlRegex.Match(rawText, lastPosition);
matches[1] = EmailRegex.Match(rawText, lastPosition);
matches[2] = PhoneRegex.Match(rawText, lastPosition);
var firstMatch = matches.Where(x => x.Success).OrderBy(x => x.Index).FirstOrDefault();
if(firstMatch == matches[0])
{
// the first match is an URL
CreateRunElement(textBlock, rawText, lastPosition, firstMatch.Index);
lastPosition = CreateUrlElement(textBlock, firstMatch);
}
else if(firstMatch == matches[1])
{
// the first match is an email
CreateRunElement(textBlock, rawText, lastPosition, firstMatch.Index);
lastPosition = CreateContactElement(textBlock, firstMatch, null);
}
else if(firstMatch == matches[2])
{
// the first match is a phonenumber
CreateRunElement(textBlock, rawText, lastPosition, firstMatch.Index);
lastPosition = CreateContactElement(textBlock, null, firstMatch);
}
else
{
// no match, we add the whole text
textBlock.Inlines.Add(new Run { Text = rawText.Substring(lastPosition) });
lastPosition = rawText.Length;
}
}
while(lastPosition < rawText.Length);
}
// -------------------------------------------------------------------------------------------
/// <summary>
/// This method will extract a fragment of the raw text string, create a Run element with the fragment and
/// add it to the textblock inlines collection
/// </summary>
/// <param name="textBlock">the textblock where to add the run element</param>
/// <param name="rawText">the raw text where the fragment will be extracted</param>
/// <param name="startPosition">the start position to extract the fragment</param>
/// <param name="endPosition">the end position to extract the fragment</param>
private static void CreateRunElement(Windows.UI.Xaml.Controls.TextBlock textBlock, string rawText, int startPosition, int endPosition)
{
var fragment = rawText.Substring(startPosition, endPosition - startPosition);
textBlock.Inlines.Add(new Run { Text = fragment });
}
// -------------------------------------------------------------------------------------------
/// <summary>
/// Create an URL element with the provided match result from the URL regex
/// It will create the Hyperlink element that will contain the URL and add it to the provided textblock
/// </summary>
/// <param name="textBlock">the textblock where to add the hyperlink</param>
/// <param name="urlMatch">the match for the URL to use to create the hyperlink element</param>
/// <returns>the newest position on the source string for the parsing</returns>
private static int CreateUrlElement(Windows.UI.Xaml.Controls.TextBlock textBlock, Match urlMatch)
{
Uri targetUri;
if(Uri.TryCreate(urlMatch.Value, UriKind.RelativeOrAbsolute, out targetUri))
{
var link = new Hyperlink();
link.Inlines.Add(new Run { Text= urlMatch.Value });
if(targetUri.IsAbsoluteUri)
link.NavigateUri = targetUri;
else
link.NavigateUri = new Uri(RelativeUriDefaultPrefix + targetUri.OriginalString);
textBlock.Inlines.Add(link);
}
else
{
textBlock.Inlines.Add(new Run { Text= urlMatch.Value });
}
return urlMatch.Index + urlMatch.Length;
}
// -------------------------------------------------------------------------------------------
/// <summary>
/// Create a hyperlink element with the provided match result from the regex that will open the contact application
/// with the provided contact information (it should be a phone number or an email address
/// This is used only if the email address / phone number is not prefixed with the mailto: / tel: scheme
/// It will create the Hyperlink element that will contain the email/phone number hyperlink and add it to the provided textblock.
/// Clicking on the link will open the contact application
/// </summary>
/// <param name="textBlock">the textblock where to add the hyperlink</param>
/// <param name="emailMatch">the match for the email to use to create the hyperlink element. Set to null if not available but at least one of emailMatch and phoneMatch must be not null.</param>
/// <param name="phoneMatch">the match for the phone number to create the hyperlink element. Set to null if not available but at least one of emailMatch and phoneMatch must be not null.</param>
/// <returns>the newest position on the source string for the parsing</returns>
private static int CreateContactElement(Windows.UI.Xaml.Controls.TextBlock textBlock, Match emailMatch, Match phoneMatch)
{
var currentMatch = emailMatch ?? phoneMatch;
var link = new Hyperlink();
link.Inlines.Add(new Run { Text= currentMatch.Value });
link.Click += (s, a) =>
{
var contact = new Contact();
if(emailMatch != null) contact.Emails.Add(new ContactEmail { Address = emailMatch.Value });
if(phoneMatch != null) contact.Phones.Add(new ContactPhone { Number = phoneMatch.Value.StripNonDigitsCharacters() });
ContactManager.ShowFullContactCard(contact, new FullContactCardOptions());
};
textBlock.Inlines.Add(link);
return currentMatch.Index + currentMatch.Length;
}
// -------------------------------------------------------------------------------------------
/// <summary>
/// Return the InteractiveText value on the provided object
/// </summary>
/// <param name="obj">the object to query</param>
/// <returns>the InteractiveText value</returns>
public static string GetInteractiveText(DependencyObject obj)
{
return (string) obj.GetValue(InteractiveTextProperty);
}
// -------------------------------------------------------------------------------------------
/// <summary>
/// SEt the InteractiveText value on the provided object
/// </summary>
/// <param name="obj">the object to query</param>
/// <param name="value">the value to set</param>
public static void SetInteractiveText(DependencyObject obj, string value)
{
obj.SetValue(InteractiveTextProperty, value);
}
What is the best way to handle the windows phone software buttons that pop up overlaying my app content.Earlier they had these hardware buttons(Back, Windows,Search Buttons) but now in some devices they have introduced software keys.Example Lumia 730 device.
There are 2 ways:
1) You can set the app or the page to auto-resize layout using
Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().SetDesiredBoundsMode(Windows.UI.ViewManagement.ApplicationViewBoundsMode.UseVisible);
However by doing this, AppBar will affect layout if ClosedDisplayMode changes...
2) To overcome problem 1, I've created the following helper class, which notifies if software buttons are being shown or not, and also gives the page's height that's being occluded by software buttons. This allows me to adjust page contents exclusively affected/hidden by software buttons.
/// <summary>
/// Handles software buttons events on Windows Phone
/// Use this to control to show occluded parts if software buttons are being shown
/// and Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().DesiredBoundsMode are set to "UseCoreWindow"
/// </summary>
public static class SoftwareButtonsHelper
{
public delegate void SoftwareButtonsChangedDelegate(Visibility softwareButtonsVisibility, double diffSize);
/// <summary>
/// Raised when software buttons visibility changes
/// </summary>
public static event SoftwareButtonsChangedDelegate SoftwareButtonsChanged;
/// <summary>
/// Current window bottom size
/// </summary>
private static double currentBottonSize = 0.0;
/// <summary>
/// Are software buttons being monitored?
/// </summary>
public static bool Listening { get; private set; }
/// <summary>
/// Software buttons visibility
/// </summary>
public static Visibility SoftwareButtonsVisibility { get; private set; }
/// <summary>
/// Current page height that's being occluded
/// </summary>
public static double CurrentInvisibleDifference { get; private set; }
/// <summary>
/// Start listening for software buttons
/// (event will raise if they appear/disappear)
/// </summary>
public static void Listen()
{
if (!Listening)
{
currentBottonSize = ApplicationView.GetForCurrentView().VisibleBounds.Bottom;
ApplicationView.GetForCurrentView().VisibleBoundsChanged += (ApplicationView sender, object args) =>
{
if (currentBottonSize != ApplicationView.GetForCurrentView().VisibleBounds.Bottom)
{
currentBottonSize = ApplicationView.GetForCurrentView().VisibleBounds.Bottom;
var currentPageAppBar = ((Window.Current.Content as Frame).Content as Page).BottomAppBar;
var isAppBarVisible = currentPageAppBar != null && currentPageAppBar.Visibility == Visibility.Visible;
var diff = Window.Current.Bounds.Bottom - currentBottonSize;
var diffAppBar = diff;
if (isAppBarVisible)
{
diffAppBar = Math.Round(diff - currentPageAppBar.Height);
diff = diffAppBar;
}
else
{
diff = Math.Round(diff);
}
if ((isAppBarVisible && diffAppBar == 0)
|| (!isAppBarVisible && diff == 0))
{
// Either contents are visible or are occluded by app bar, do nothing..
OnSoftwareButtonsChanged(Visibility.Collapsed, diff);
}
else
{
// Software buttons are active...
OnSoftwareButtonsChanged(Visibility.Visible, diff);
}
}
};
Listening = true;
}
}
/// <summary>
/// Raise event
/// </summary>
/// <param name="newVisibility"></param>
/// <param name="difference"></param>
private static void OnSoftwareButtonsChanged(Visibility newVisibility, double difference)
{
CurrentInvisibleDifference = difference;
if (SoftwareButtonsVisibility != newVisibility)
{
SoftwareButtonsVisibility = newVisibility;
if (SoftwareButtonsChanged != null)
{
SoftwareButtonsChanged(newVisibility, difference);
}
}
}
}
You just need to call SoftwareButtonsHelper.Listen(); AFTER Window.Current.Activate(); on App.xaml.cs and subscribe SoftwareButtonsHelper.SoftwareButtonsChanged on the target(s) page(s).
Hope it helps!
I have the following CustomControl that I want to use as a base for a Banner:
XAML:
<UserControl x:Name="userControl"
x:Class="Nova.WinRT.Controls.BannerPanel"
...>
<Grid Background="#BF8B8B8B">
<Grid Height="400" Background="#FF050A7C" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TextBlock x:Name="_Title" Text="Title" FontSize="30" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<ContentPresenter x:Name="_Content" Grid.Row="1"/>
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Center">
<Button x:Name="_OK" Content="OK" HorizontalAlignment="Center" Click="OkPressed" FontSize="18" Width="100" Margin="20,0" />
<Button x:Name="_Cancel" Content="Cancel" HorizontalAlignment="Center" Click="CancelPressed" FontSize="18" Width="100" Margin="20,0" />
</StackPanel>
</Grid>
</Grid>
</UserControl>
C#:
public sealed partial class BannerPanel : UserControl
{
/// <summary>
/// Title of the Banner
/// </summary>
public string Title
{
get { return _Title.Text; }
set { _Title.Text = value; }
}
/// <summary>
/// The visibility of the OK button
/// </summary>
public Visibility OKVisibility
{
get { return _OK.Visibility; }
set { _OK.Visibility = value; }
}
/// <summary>
/// The visibility of the Cancel button
/// </summary>
public Visibility CancelVisibility
{
get { return _Cancel.Visibility; }
set { _Cancel.Visibility = value; }
}
/// <summary>
/// The inner content of the panel
/// </summary>
public FrameworkElement InnerContent
{
get { return (FrameworkElement)_Content.Content; }
set { _Content.Content = value; }
}
/// <summary>
/// Fires when the Ok button is clicked
/// </summary>
public event RoutedEventHandler OkClick;
/// <summary>
/// Fires when the Cancel button is clicked
/// </summary>
public event RoutedEventHandler CancelClick;
};
But when I use it (see the following XAML), the autogenerated code does not find the elements in the inner content:
XAML:
<Controls:BannerPanel x:Name="Banner" Title="Terms and Policy" CancelVisibility="Collapsed" OkClick="OnTermsAccepted">
<Controls:BannerPanel.InnerContent>
<ScrollViewer Width="500">
<TextBlock x:Name="TermsText" TextWrapping="Wrap" FontSize="12" />
</ScrollViewer>
</Controls:BannerPanel.InnerContent>
</Controls:BannerPanel>
C#:
public sealed partial class TermsBanner : UserControl
{
/// <summary>
/// Constructor
/// </summary>
public TermsBanner()
{
this.InitializeComponent();
// Why do I have to find TermsText Manually like this??????
TermsText = (Banner.InnerContent as ScrollViewer).Content as TextBlock;
TermsText.Text = TermsOfUse;
}
};
Why do I have to manually point the variable TermsText to the correct thing? Why can't it automatically find it with FindName() like it usually does?
You should define DependencyProperty properties instead of regular ones.
Derive from ContentControl instead of UserControl.
Put your XAML in Themes/Generic.xaml in a Style/Setter/Property="Template" Value=".... It happens automatically if you create the control using the Templated Control VS item template.
Use TemplateBinding to bind properties of inner template elements to your control properties.
FindName might not work across namescopes. I never use it.
excuse the typos. Typed on a phone.
Here's a solution:
Created templated control (also known us custom control - not to be confused with a UserControl) and modified it to derive from ContentControl instead of Control:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
// The Templated Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234235
namespace App124
{
[TemplatePart(Name = "_OK", Type = typeof(Button))]
[TemplatePart(Name = "_Cancel", Type = typeof(Button))]
public sealed class BannerPanel : ContentControl
{
#region Title
/// <summary>
/// Title Dependency Property
/// </summary>
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register(
"Title",
typeof(string),
typeof(BannerPanel),
new PropertyMetadata(null, OnTitleChanged));
/// <summary>
/// Gets or sets the Title property. This dependency property
/// indicates ....
/// </summary>
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
/// <summary>
/// Handles changes to the Title property.
/// </summary>
/// <param name="d">
/// The <see cref="DependencyObject"/> on which
/// the property has changed value.
/// </param>
/// <param name="e">
/// Event data that is issued by any event that
/// tracks changes to the effective value of this property.
/// </param>
private static void OnTitleChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (BannerPanel)d;
string oldTitle = (string)e.OldValue;
string newTitle = target.Title;
target.OnTitleChanged(oldTitle, newTitle);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes
/// to the Title property.
/// </summary>
/// <param name="oldTitle">The old Title value</param>
/// <param name="newTitle">The new Title value</param>
private void OnTitleChanged(
string oldTitle, string newTitle)
{
}
#endregion
#region OKVisibility
/// <summary>
/// OKVisibility Dependency Property
/// </summary>
public static readonly DependencyProperty OKVisibilityProperty =
DependencyProperty.Register(
"OKVisibility",
typeof(Visibility),
typeof(BannerPanel),
new PropertyMetadata(Visibility.Visible, OnOKVisibilityChanged));
/// <summary>
/// Gets or sets the OKVisibility property. This dependency property
/// indicates ....
/// </summary>
public Visibility OKVisibility
{
get { return (Visibility)GetValue(OKVisibilityProperty); }
set { SetValue(OKVisibilityProperty, value); }
}
/// <summary>
/// Handles changes to the OKVisibility property.
/// </summary>
/// <param name="d">
/// The <see cref="DependencyObject"/> on which
/// the property has changed value.
/// </param>
/// <param name="e">
/// Event data that is issued by any event that
/// tracks changes to the effective value of this property.
/// </param>
private static void OnOKVisibilityChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (BannerPanel)d;
Visibility oldOKVisibility = (Visibility)e.OldValue;
Visibility newOKVisibility = target.OKVisibility;
target.OnOKVisibilityChanged(oldOKVisibility, newOKVisibility);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes
/// to the OKVisibility property.
/// </summary>
/// <param name="oldOKVisibility">The old OKVisibility value</param>
/// <param name="newOKVisibility">The new OKVisibility value</param>
private void OnOKVisibilityChanged(
Visibility oldOKVisibility, Visibility newOKVisibility)
{
}
#endregion
#region CancelVisibility
/// <summary>
/// CancelVisibility Dependency Property
/// </summary>
public static readonly DependencyProperty CancelVisibilityProperty =
DependencyProperty.Register(
"CancelVisibility",
typeof(Visibility),
typeof(BannerPanel),
new PropertyMetadata(Visibility.Visible, OnCancelVisibilityChanged));
/// <summary>
/// Gets or sets the CancelVisibility property. This dependency property
/// indicates ....
/// </summary>
public Visibility CancelVisibility
{
get { return (Visibility)GetValue(CancelVisibilityProperty); }
set { SetValue(CancelVisibilityProperty, value); }
}
/// <summary>
/// Handles changes to the CancelVisibility property.
/// </summary>
/// <param name="d">
/// The <see cref="DependencyObject"/> on which
/// the property has changed value.
/// </param>
/// <param name="e">
/// Event data that is issued by any event that
/// tracks changes to the effective value of this property.
/// </param>
private static void OnCancelVisibilityChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (BannerPanel)d;
Visibility oldCancelVisibility = (Visibility)e.OldValue;
Visibility newCancelVisibility = target.CancelVisibility;
target.OnCancelVisibilityChanged(oldCancelVisibility, newCancelVisibility);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes
/// to the CancelVisibility property.
/// </summary>
/// <param name="oldCancelVisibility">The old CancelVisibility value</param>
/// <param name="newCancelVisibility">The new CancelVisibility value</param>
private void OnCancelVisibilityChanged(
Visibility oldCancelVisibility, Visibility newCancelVisibility)
{
}
#endregion
/// <summary>
/// Fires when the Ok button is clicked
/// </summary>
public event RoutedEventHandler OkClick;
/// <summary>
/// Fires when the Cancel button is clicked
/// </summary>
public event RoutedEventHandler CancelClick;
public BannerPanel()
{
this.DefaultStyleKey = typeof(BannerPanel);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
var cancelButton = (Button)GetTemplateChild("_Cancel");
var okButton = (Button)GetTemplateChild("_OK");
cancelButton.Click += CancelClick;
okButton.Click += OkClick;
}
}
}
Updated Themes/Generic.xaml to this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App124">
<Style
TargetType="local:BannerPanel">
<Setter
Property="HorizontalContentAlignment"
Value="Left" />
<Setter
Property="VerticalContentAlignment"
Value="Top" />
<Setter
Property="Title"
Value="Title" />
<Setter
Property="OKVisibility"
Value="Visible" />
<Setter
Property="CancelVisibility"
Value="Visible" />
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="local:BannerPanel">
<Grid
Background="#BF8B8B8B">
<Grid
Height="400"
Background="#FF050A7C"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition
Height="50" />
<RowDefinition
Height="*" />
<RowDefinition
Height="50" />
</Grid.RowDefinitions>
<TextBlock
x:Name="_Title"
Text="{TemplateBinding Title}"
FontSize="30"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<ContentPresenter
Grid.Row="1"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
<StackPanel
Orientation="Horizontal"
Grid.Row="2"
HorizontalAlignment="Center">
<Button
x:Name="_OK"
Content="OK"
Visibility="{TemplateBinding OKVisibility}"
HorizontalAlignment="Center"
FontSize="18"
Width="100"
Margin="20,0" />
<Button
x:Name="_Cancel"
Content="Cancel"
Visibility="{TemplateBinding CancelVisibility}"
HorizontalAlignment="Center"
FontSize="18"
Width="100"
Margin="20,0" />
</StackPanel>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
This is how it's used in MainPage.xaml:
<Page
x:Class="App124.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App124"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid
Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<local:BannerPanel
x:Name="Banner"
Title="Terms and Policy"
CancelVisibility="Collapsed"
OkClick="OnTermsAccepted">
<ScrollViewer
Width="500">
<TextBlock
x:Name="TermsText"
Text="Terms and Conditions"
TextWrapping="Wrap"
FontSize="12" />
</ScrollViewer>
</local:BannerPanel>
</Grid>
</Page>
and MainPage.xaml.cs code behind:
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace App124
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void OnTermsAccepted(object sender, RoutedEventArgs e)
{
new MessageDialog(TermsText.Text).ShowAsync();
}
}
}
I have the following in my root ResourceDictionary. The Foreground = Red part works, but the custom attached dependency property does not get set.
I can set it manually through code, but I obviously want to avoid having to set it for every textbox. Does this work in Silverlight? I have seen some posts about doing it in WPF, and my code looks right (to me).
<Style TargetType="TextBox">
<Setter Property="controls:TextBoxContextMenu.HasContextMenu" Value="True" />
<Setter Property="Foreground" Value="Red" />
</Style>
/// <summary>
/// Gets the value of the HasContextMenu attached property for a specified TextBox.
/// </summary>
/// <param name="element">The TextBox from which the property value is read.</param>
/// <returns>The HasContextMenu property value for the TextBox.</returns>
public static bool GetHasContextMenu(TextBox element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(HasContextMenuProperty);
}
/// <summary>
/// Sets the value of the HasContextMenu attached property to a specified TextBox.
/// </summary>
/// <param name="element">The TextBox to which the attached property is written.</param>
/// <param name="value">The needed HasContextMenu value.</param>
public static void SetHasContextMenu(TextBox element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(HasContextMenuProperty, value);
}
/// <summary>
/// Identifies the HasContextMenu dependency property.
/// </summary>
public static readonly DependencyProperty HasContextMenuProperty =
DependencyProperty.RegisterAttached(
"HasContextMenu",
typeof(bool),
typeof(TextBox),
new PropertyMetadata(false, OnHasContextMenuPropertyChanged));
/// <summary>
/// HasContextMenuProperty property changed handler.
/// </summary>
/// <param name="d">TextBoxContextMenu that changed its HasContextMenu.</param>
/// <param name="e">Event arguments.</param>
private static void OnHasContextMenuPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// code
}
I should add that the attached dependency property is defined in a class which inherits from RadContextMenu, which is a DependencyObject (I have been reading and somewhere it is suggested that this can't work if the attached property is defined in such a class, but this seems strange)
I figured it out. It was indeed due to having the attached property defined in the class I have.
To fix it, i created a new class called TextBoxContextMenuService and put the code in there instead.