I try to extend my app with the missing CharacterEllipsis for TextBlocks in my Windows Phone 8 app. I found a nice solution here: http://nerdplusart.com/texttrimming-textblock-for-silverlight/.
So I implemented this in my code:
public class DynamicTextBlock : ContentControl
{
#region Text (DependencyProperty)
/// <summary>
/// Gets or sets the Text DependencyProperty. This is the text that will be displayed.
/// </summary>
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(DynamicTextBlock),
new PropertyMetadata(null, new PropertyChangedCallback(OnTextChanged)));
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DynamicTextBlock)d).OnTextChanged(e);
}
protected virtual void OnTextChanged(DependencyPropertyChangedEventArgs e)
{
this.InvalidateMeasure();
}
#endregion
#region TextWrapping (DependencyProperty)
/// <summary>
/// Gets or sets the TextWrapping property. This corresponds to TextBlock.TextWrapping.
/// </summary>
public TextWrapping TextWrapping
{
get { return (TextWrapping)GetValue(TextWrappingProperty); }
set { SetValue(TextWrappingProperty, value); }
}
public static readonly DependencyProperty TextWrappingProperty =
DependencyProperty.Register("TextWrapping", typeof(TextWrapping), typeof(DynamicTextBlock),
new PropertyMetadata(TextWrapping.NoWrap, new PropertyChangedCallback(OnTextWrappingChanged)));
private static void OnTextWrappingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DynamicTextBlock)d).OnTextWrappingChanged(e);
}
protected virtual void OnTextWrappingChanged(DependencyPropertyChangedEventArgs e)
{
this.textBlock.TextWrapping = (TextWrapping)e.NewValue;
this.InvalidateMeasure();
}
#endregion
#region LineHeight (DependencyProperty)
/// <summary>
/// Gets or sets the LineHeight property. This property corresponds to TextBlock.LineHeight;
/// </summary>
public double LineHeight
{
get { return (double)GetValue(LineHeightProperty); }
set { SetValue(LineHeightProperty, value); }
}
public static readonly DependencyProperty LineHeightProperty =
DependencyProperty.Register("LineHeight", typeof(double), typeof(DynamicTextBlock),
new PropertyMetadata(0.0, new PropertyChangedCallback(OnLineHeightChanged)));
private static void OnLineHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DynamicTextBlock)d).OnLineHeightChanged(e);
}
protected virtual void OnLineHeightChanged(DependencyPropertyChangedEventArgs e)
{
textBlock.LineHeight = LineHeight;
this.InvalidateMeasure();
}
#endregion
#region LineStackingStrategy (DependencyProperty)
/// <summary>
/// Gets or sets the LineStackingStrategy DependencyProperty. This corresponds to TextBlock.LineStackingStrategy.
/// </summary>
public LineStackingStrategy LineStackingStrategy
{
get { return (LineStackingStrategy)GetValue(LineStackingStrategyProperty); }
set { SetValue(LineStackingStrategyProperty, value); }
}
public static readonly DependencyProperty LineStackingStrategyProperty =
DependencyProperty.Register("LineStackingStrategy", typeof(LineStackingStrategy), typeof(DynamicTextBlock),
new PropertyMetadata(LineStackingStrategy.BlockLineHeight, new PropertyChangedCallback(OnLineStackingStrategyChanged)));
private static void OnLineStackingStrategyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DynamicTextBlock)d).OnLineStackingStrategyChanged(e);
}
protected virtual void OnLineStackingStrategyChanged(DependencyPropertyChangedEventArgs e)
{
this.textBlock.LineStackingStrategy = (LineStackingStrategy)e.NewValue;
this.InvalidateMeasure();
}
#endregion
/// <summary>
/// A TextBlock that gets set as the control's content and is ultiately the control
/// that displays our text
/// </summary>
private TextBlock textBlock;
/// <summary>
/// Initializes a new instance of the DynamicTextBlock class
/// </summary>
public DynamicTextBlock()
{
// create our textBlock and initialize
this.textBlock = new TextBlock();
this.Content = this.textBlock;
}
/// <summary>
/// Handles the measure part of the measure and arrange layout process. During this process
/// we measure the textBlock that we've created as content with increasingly smaller amounts
/// of text until we find text that fits.
/// </summary>
/// <param name="availableSize">the available size</param>
/// <returns>the base implementation of Measure</returns>
protected override Size MeasureOverride(Size availableSize)
{
// just to make the code easier to read
bool wrapping = this.TextWrapping == TextWrapping.Wrap;
Size unboundSize = wrapping ? new Size(availableSize.Width, double.PositiveInfinity) : new Size(double.PositiveInfinity, availableSize.Height);
string reducedText = this.Text;
// set the text and measure it to see if it fits without alteration
this.textBlock.Text = reducedText;
Size textSize = base.MeasureOverride(unboundSize);
while (wrapping ? textSize.Height > availableSize.Height : textSize.Width > availableSize.Width)
{
int prevLength = reducedText.Length;
reducedText = this.ReduceText(reducedText);
if (reducedText.Length == prevLength)
{
break;
}
this.textBlock.Text = reducedText + "...";
textSize = base.MeasureOverride(unboundSize);
}
return base.MeasureOverride(availableSize);
}
/// <summary>
/// Reduces the length of the text. Derived classes can override this to use different techniques
/// for reducing the text length.
/// </summary>
/// <param name="text">the original text</param>
/// <returns>the reduced length text</returns>
protected virtual string ReduceText(string text)
{
return text.Substring(0, text.Length - 1);
}
}
And in my XAML I use this like this:
<myNamespace:DynamicTextBlock Grid.Column="0" Text="Dies ist ein Text der sehr lang ist und umebrochen wird" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="-1,0,0,0" Foreground="{Binding IsHighlighted, Converter={StaticResource BoolToBrushConverter}}" FontSize="32" />
Everything works cool. Now I want to apply a Style to this. But the following doesn´t work:
Style="{StaticResource PhoneTextLargeStyle}"
How the solution must be Extended to accept a StaticRessource as Style?
Styles in Windows Phone have a TargetType and they can only be set to controls of that type or its subtypes (child classes). PhoneTextLargeStyle's target is TextBlock, so you can't put it on other controls (like your DynamicTextBlock).
You would need to create a new style which targets DynamicTextBlock and sets the values that you like. I guess you can find out one way or another what the PhoneTextLargeStyle style does.
Related
I am creating a custom control in Xamarin.Forms which is inherited from TemplatedView and using the application in all three platforms. For testing purposes, I need to set Automation ID for all native controls.
Usually, by using AutomationPeer we can set Automation ID to native UWP control from the UWP renderer project. But, in my custom control, I get this. Control is null always.
So, I can’t set Automation ID in UWP renderer class as Forms TemplatedView is a FrameworkElement in UWP.
My query is,
How to get a native element (this.Control) in UWP renderer project for Xamarin.Forms TemplatedView.
How to set Automation Id to native UWP control if this.Control is null.
The code snippet is provided below:
class FormsCustomLayout : TemplatedView
{
public static readonly BindableProperty InputViewProperty =
BindableProperty.Create(nameof(InputView), typeof(View), typeof(FormsCustomLayout), null, BindingMode.Default, null, OnInputViewChanged);
private static void OnInputViewChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as FormsCustomLayout).OnInputViewChanged(oldValue, newValue);
}
private void OnInputViewChanged(object oldValue, object newValue)
{
var oldView = (View)oldValue;
if (oldView != null)
{
if (this.contentGrid.Children.Contains(oldView))
{
oldView.SizeChanged -= OnInputViewSizeChanged;
oldView.BindingContext = null;
this.contentGrid.Children.Remove(oldView);
}
}
var newView = (View)newValue;
if (newView != null)
{
newView.SizeChanged += OnInputViewSizeChanged;
newView.VerticalOptions = LayoutOptions.CenterAndExpand;
newView.HeightRequest = 60;
this.contentGrid.Children.Add(newView);
}
}
private void OnInputViewSizeChanged(object sender, EventArgs e)
{
}
public View InputView
{
get { return (View)GetValue(InputViewProperty); }
set { SetValue(InputViewProperty, value); }
}
internal void UpdateText(object text)
{
this.Text = (string)text;
}
private readonly Grid contentGrid = new Grid();
public FormsCustomLayout()
{
this.ControlTemplate = new ControlTemplate(typeof(StackLayout));
((StackLayout)Children[0]).Children.Add(this.contentGrid);
if (InputView != null)
{
this.contentGrid.Children.Add(InputView);
}
}
internal string Text { get; private set; }
}
The custom renderer code snippet is provided below:
class FormsCustomLayoutRenderer : ViewRenderer<FormsCustomLayout, FrameworkElement>
{
/// <summary>
/// Method that is called when the automation id is set.
/// </summary>
/// <param name="id">The automation id.</param>
protected override void SetAutomationId(string id)
{
if (this.Control == null)
{
base.SetAutomationId(id);
}
else
{
this.SetAutomationPropertiesAutomationId(id);
this.Control.SetAutomationPropertiesAutomationId(id);
}
}
/// <summary>
/// Provide automation peer for the control
/// </summary>
/// <returns>The TextInputLayout view automation peer.</returns>
protected override AutomationPeer OnCreateAutomationPeer()
{
if (this.Control == null)
{
return new FormsCustomLayoutAutomationPeer(this);
}
return new FormsCustomLayoutAutomationPeer(this.Control);
}
protected override void OnElementChanged(ElementChangedEventArgs<FormsCustomLayout> e)
{
base.OnElementChanged(e);
var element = e.NewElement;
}
}
internal class FormsCustomLayoutAutomationPeer : FrameworkElementAutomationPeer
{
/// <summary>
/// Initializes a new instance of the <see cref="FormsCustomLayoutRenderer"/> class.
/// </summary>
/// <param name="owner">FormsCustomLayout View control</param>
public FormsCustomLayoutAutomationPeer(FrameworkElement owner) : base(owner)
{
}
/// <summary>
/// Describe the control type
/// </summary>
/// <returns>The control type.</returns>
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Custom;
}
/// <summary>
/// Describe the class name.
/// </summary>
/// <returns>The Class Name.</returns>
protected override string GetClassNameCore()
{
return "FormsCustomLayout";
}
}
Demo sample attached here: FormsCustomControl
Please share your idea or solution to set Automation ID.
Regards,
Bharathiraja.
I have the below code which initiates the browser in each test. However the first Test runs OK correctly starting the browser. The second Test on wards start to fail
public class BrowserFactory
{
private static IWebDriver driver;
public static IWebDriver Driver
{
get
{
if (driver == null)
throw new NullReferenceException("The WebDriver browser instance was not initialized. You should first call the method InitBrowser.");
return driver;
}
set
{
driver = value;
}
}
public static void InitBrowser(string browserName)
{
switch (browserName)
{
case "Firefox":
if (driver == null)
{
driver = new FirefoxDriver();
driver.Manage().Window.Maximize();
}
break;
case "IE":
if (driver == null)
{
driver = new InternetExplorerDriver();
driver.Manage().Window.Maximize();
}
break;
case "Chrome":
if (driver == null)
{
driver = new ChromeDriver();
driver.Manage().Window.Maximize();
}
break;
}
}
And this is how I initiate and quit browser in each test
[TestFixture]
public class AdminUserHasAccessToDashboard
{
[SetUp]
public void GotoHomePage()
{
BrowserFactory.InitBrowser("Chrome");
BrowserFactory.LoadApplication(ConfigurationManager.AppSettings["URL"]);
}
[TearDown]
public void Quit()
{
BrowserFactory.CloseAllDrivers();
}
It looks like you are probably setting driver instance in your first test and other tests are false on if (driver == null), so other instances will never be created. Your code is incomplete so I can't be sure, but it seems to be an issue here.
How are you closing your driver after first test is completed?
Include the method mentioned below in your browser factory and call at the end of each test, or set up before the tests are run then use the single instance for each test and tear down at the end.
public static void Quit(){
if (Driver!= null)
Driver.Quit();
}
I use a static "Driver" class which is referenced in each test class (not each test case)
I include this in each class(they are nunit C# tests)
#region Initialise and clean up
[OneTimeSetUp]
public void Init()
{
Driver.Initialise();
}
[OneTimeTearDown]
public void CleanUp()
{
Driver.Quit();
}
#endregion
Then my driver class is as below, the appstate region wont apply but can be edited to suit your needs.
using System;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using AdaptiveAds_TestFramework.PageFrameworks;
namespace AdaptiveAds_TestFramework.Helpers
{
/// <summary>
/// Contains functionality for browser automation.
/// </summary>
public static class Driver
{
#region Variables
private static IWebDriver _instance;
private static Period _waitPeriod;
#endregion//Variables
#region Properties
/// <summary>
/// Browser automation object.
/// </summary>
public static IWebDriver Instance
{
get { return _instance; }
set { _instance = value; SetWait(WaitPeriod); }
}
/// <summary>
/// Duration automation framework must wait before throwing an error.
/// </summary>
public static Period WaitPeriod
{
get { return _waitPeriod; }
set
{
_waitPeriod = value;
SetWait(_waitPeriod);
}
}
#endregion//Properties
#region Methods
#region set-up and tear-down
/// <summary>
/// Sets up a new automation object.
/// </summary>
public static void Initialise()
{
Quit();
Instance = new FirefoxDriver(new FirefoxBinary(ConfigData.FireFoxPath), new FirefoxProfile());
Instance.Manage().Window.Maximize();
WaitPeriod = ConfigData.DefaultWaitPeriod;
}
/// <summary>
/// Disposes of the automation object.
/// </summary>
public static void Quit()
{
if (Instance != null)
Instance.Quit();
}
#endregion //set-up and tear-down
#region NavigableLocations
/// <summary>
/// Navigate browser to a given location.
/// </summary>
/// <param name="location">Location to navigate to.</param>
/// <param name="logInIfNeeded">Logs in if authentication is required.</param>
/// <param name="errorIfNotReached">Errors if the location was not reached.</param>
public static void GoTo(Location location, bool logInIfNeeded, bool errorIfNotReached)
{
// Navigate browser to the location.
Instance.Navigate().GoToUrl(Helper.RouteUrl(location));
Thread.Sleep(500);// wait for system to navigate
bool needToLogIn = false;
if (logInIfNeeded)
{
try
{
IsAt(Location.Login);
needToLogIn = true;
}
catch
{
// Not at login page so Login not needed.
}
if (needToLogIn)
{
LoginPage.LoginAs(ConfigData.Username).WithPassword(ConfigData.Password).Login();
GoTo(location, false, errorIfNotReached);
}
}
if (errorIfNotReached)
{
IsAt(location);
}
}
/// <summary>
/// Ensures the Driver is at the specified location, throws a WebDriverException if at another location.
/// </summary>
/// <param name="location">Location to check the browser is at.</param>
public static void IsAt(Location location)
{
string expected = Helper.RouteUrl(location);
string actual = Instance.Url;
// Check the browser is at the correct location.
if (actual != expected)
{
// Driver is not at the specified location.
throw new WebDriverException("Incorrect location.",
new InvalidElementStateException(
"The given location did not match the browser." +
" Expected \"" + expected + "\" Actual \"" + actual + "\""));
}
ActionWait(Period.None, CheckForLavarelError);
}
/// <summary>
/// Ensures the Driver is not at the specified location, throws a WebDriverException if at the location.
/// </summary>
/// <param name="location">Location to check the browser is not at.</param>
public static void IsNotAt(Location location)
{
string expected = Helper.RouteUrl(location);
string actual = Instance.Url;
// Check the browser is not at the correct location.
if (actual == expected)
{
// Driver is at the specified location.
throw new WebDriverException("Incorrect location.",
new InvalidElementStateException(
"The given location matched the browser."));
}
}
#endregion//NavigableLocations
#region WaitHandling
/// <summary>
/// Performs an action with a temporary wait period.
/// </summary>
/// <param name="waitPeriod">Period to wait while executing action.</param>
/// <param name="action">Action to execute.</param>
public static void ActionWait(Period waitPeriod, Action action)
{
// Store the current wait period
Period previousPeriod = WaitPeriod;
// Run task with given wait period
SetWait(waitPeriod);
action();
// Revert to the old wait period
SetWait(previousPeriod);
}
/// <summary>
/// Updates the Automation instance wait period.
/// </summary>
/// <param name="waitPeriod">New wait period.</param>
private static void SetWait(Period waitPeriod)
{
int miliseconds;
ConfigData.WaitPeriods.TryGetValue(waitPeriod, out miliseconds);
// Set the drivers instance to use the wait period.
if (Instance != null)
{
Instance.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromMilliseconds(miliseconds));
}
}
#endregion//WaitHandling
#region AppState
/// <summary>
/// Checks that the website hasn't crashed from the back end.
/// </summary>
public static void CheckForLavarelError()
{
bool error = false;
try
{
Instance.FindElement(By.ClassName("exception_message"));
error = true;
}
catch
{
// all good, could not find error content.
}
if (error)
{
throw new Exception("Lavarel threw an error");
}
}
/// <summary>
/// Clicks on the main menu button to open or close the main menu.
/// </summary>
public static void OpenCloseMenuBar()
{
try
{
IWebElement menuButton = Instance.FindElement(By.Name(ConfigData.MainMenuButtonName));
menuButton.Click();
}
catch (Exception e)
{
throw new NoSuchElementException("Could not find the Menu button element.", e);
}
}
/// <summary>
/// Asserts the logged in state agents the parameter.
/// </summary>
/// <param name="checkLoggedIn">Parameter to check agents logged in state.</param>
public static void LoggedIn(bool checkLoggedIn)
{
OpenCloseMenuBar();
bool signInFound;
bool signOutFound;
bool isLoggedIn = false;
try { Instance.FindElement(By.Name(ConfigData.SignInName)); signInFound = true; }
catch { signInFound = false; }
try { Instance.FindElement(By.Name(ConfigData.SignOutName)); signOutFound = true; }
catch { signOutFound = false; }
if (!signInFound && !signOutFound)
{
throw new ElementNotVisibleException("Unable to assert state due to unavailability of SignIn/Out links.");
}
if (signOutFound) isLoggedIn = true;
if (signInFound) isLoggedIn = false;
if (isLoggedIn != checkLoggedIn)
{
throw new Exception($"Logged in Expected: {checkLoggedIn} Actual: {isLoggedIn}");
}
}
/// <summary>
/// Signs out of the system.
/// </summary>
/// <param name="errorIfAlreadySignedOut">Determines whether to throw an error if already signed out.</param>
public static void SignOut(bool errorIfAlreadySignedOut = true)
{
try
{
LoggedIn(true);
}
catch (Exception)
{
if (errorIfAlreadySignedOut)
{
throw;
}
return;
}
IWebElement signOut = Instance.FindElement(By.Name(ConfigData.SignOutName));
signOut.Click();
Thread.Sleep(500);// wait for system to logout
}
#endregion//AppState
#endregion//Methods
}
}
I´m developing an ASP.NET MVC Application, in which I use NHibernate and Ninject.
The Problem is caused by the following Controller:
public class ShoppingCartController : Controller
{
private readonly Data.Infrastructure.IShoppingCartRepository _shoppingCartRepository;
private readonly Data.Infrastructure.IShopItemRepository _shopItemRepository;
public ShoppingCartController(Data.Infrastructure.IShoppingCartRepository shoppingCartController,
Data.Infrastructure.IShopItemRepository shopItemRepository)
{
_shoppingCartRepository = shoppingCartController;
_shopItemRepository = shopItemRepository;
}
public ActionResult AddToShoppingCart(FormCollection formCollection)
{
var cartItem = new Data.Models.ShoppingCartItem();
cartItem.ChangeDate = DateTime.Now;
cartItem.ShopItem = _shopItemRepository.GetShopItem(SessionData.Data.Info, Convert.ToInt32(formCollection["shopItemId"]));
//IF I DONT´T CALL THE METHOD ABOVE, AddToCart works
_shoppingCartRepository.AddToCart(SessionData.Data.Info, cartItem);
//BUT IF I CALL THE GetShopItem METHOD I GET THE EXCEPTION HERE!
return RedirectToAction("Index", "Shop");
}
}
I know most of the Time this Exception is caused by wrong Mapping, but I´m pretty sure that my Mapping is right because the AddToCart-Method works if I don´t call GetShopItem...
So here is the Code of the ShopItemRepository:
public class ShopItemRepository : ReadOnlyRepository<ShopItem>, IShopItemRepository
{
public ShopItemRepository(IUnitOfWork uow) : base(uow)
{
}
public ShopItem GetShopItem(SessionParams param, int id)
{
return CurrentSession.QueryOver<ShopItem>()
.Where(x => x.ProcessId == param.ProcessId &&
x.CatalogueId == param.CatalogueId &&
x.Id == id)
.SingleOrDefault();
}
public IList<ShopItem> GetShopItems(SessionParams param)
{
return CurrentSession.GetNamedQuery("GetShopItems")
.SetParameter("requestor_id", param.RequestorId)
.SetParameter("recipient_id", param.RecipientId)
.SetParameter("process_id", param.ProcessId)
.SetParameter("catalogue_id", param.CatalogueId)
.List<ShopItem>();
}
}
And finally the Code of my UnitOfWork (basically it is just a Wrapper for the Session because I don´t want to reference NHibernate in my MVC Project)
public class UnitOfWork : IUnitOfWork, IDisposable
{
private NHibernate.ISession _currentSession;
public NHibernate.ISession CurrentSession
{
get
{
if(_currentSession == null)
{
_currentSession = SessionFactoryWrapper.SessionFactory.OpenSession();
}
return _currentSession;
}
}
public void Dispose()
{
if(_currentSession != null)
{
_currentSession.Close();
_currentSession.Dispose();
_currentSession = null;
}
GC.SuppressFinalize(this);
}
}
Addendum:
My NinjectWebCommon Class
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Stops the application.
/// </summary>
public static void Stop()
{
bootstrapper.ShutDown();
}
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
return kernel;
}
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();
kernel.Bind<Data.Infrastructure.ICatalogueRepository>().To<Data.Repositories.CatalogueRepository>();
kernel.Bind<Data.Infrastructure.ICategoryRepository>().To<Data.Repositories.CategoryRepository>();
kernel.Bind<Data.Infrastructure.IContactRepository>().To<Data.Repositories.ContactRepository>();
kernel.Bind<Data.Infrastructure.IProcessRepository>().To<Data.Repositories.ProcessRepository>();
kernel.Bind<Data.Infrastructure.IShopItemRepository>().To<Data.Repositories.ShopItemRepository>();
kernel.Bind<Data.Infrastructure.IShoppingCartRepository>().To<Data.Repositories.ShoppingCartRepository>();
}
}
IUnitOfWork is set to RequestScope so in the Case of ShoppingCartController, the two Repositories share the same UOW right?
Maybe this could cause the Problem?
Are you sure that this isn´t caused by wrong mapping? I had the same Issue and could resolve it by checking my mappings again!
In WinRT Applications, when you right click a ListBoxItem the AppBar is shown. But when you right click a GridViewItem the AppBar doesn't appear. Can this be configured?
If not is it beter to work with a ListBox instead of GridView and customize the templates. Or should I implement it With a RightTapped command.
(I work with MVVM Light, since Caliburn.Micro is currently not working)
Example of RightTappedCommand:
public sealed class RightTapped
{
#region Properties
#region Command
///
/// GetCommand
///
///
///
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
///
/// SetCommand
///
///
///
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
///
/// DependencyProperty CommandProperty
///
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(RightTapped), new PropertyMetadata(null, OnCommandChanged));
#endregion Command
#region CommandParameter
///
/// GetCommandParameter
///
///
///
public static object GetCommandParameter(DependencyObject obj)
{
return (object)obj.GetValue(CommandParameterProperty);
}
///
/// SetCommandParameter
///
///
///
public static void SetCommandParameter(DependencyObject obj, object value)
{
obj.SetValue(CommandParameterProperty, value);
}
///
/// DependencyProperty CommandParameterProperty
///
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(RightTapped), new PropertyMetadata(null, OnCommandParameterChanged));
#endregion CommandParameter
#region HasCommandParameter
private static bool GetHasCommandParameter(DependencyObject obj)
{
return (bool)obj.GetValue(HasCommandParameterProperty);
}
private static void SetHasCommandParameter(DependencyObject obj, bool value)
{
obj.SetValue(HasCommandParameterProperty, value);
}
private static readonly DependencyProperty HasCommandParameterProperty =
DependencyProperty.RegisterAttached("HasCommandParameter", typeof(bool), typeof(RightTapped), new PropertyMetadata(false));
#endregion HasCommandParameter
#endregion Propreties
#region Event Handling
private static void OnCommandParameterChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SetHasCommandParameter(o, true);
}
private static void OnCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var element = o as FrameworkElement;
if (element != null)
{
if (e.NewValue == null)
{
element.RightTapped -= FrameworkElementKeyUp;
}
else if (e.OldValue == null)
{
element.RightTapped += FrameworkElementKeyUp;
}
}
}
private static void FrameworkElementKeyUp(object sender, RightTappedRoutedEventArgs e)
{
var o = sender as DependencyObject;
var command = GetCommand(sender as DependencyObject);
var element = e.OriginalSource as FrameworkElement;
if (element != null)
{
// If the command argument has been explicitly set (even to NULL)
if (GetHasCommandParameter(o))
{
var commandParameter = GetCommandParameter(o);
// Execute the command
if (command.CanExecute(commandParameter))
{
command.Execute(commandParameter);
}
}
else if (command.CanExecute(element.DataContext))
{
command.Execute(element.DataContext);
}
}
}
#endregion
}
In Xaml, something like this:
common:Tapped.Command="{Binding ShowAppBar}"
You can just do myAppBar.IsOpen = true.
It depends on the gridview selection mode, the right-click is used for selecting items. If you set the selectionmode property to None the appbar will open on right-click.
I have multiple textboxes bound to different data, but I would like every one to launch the same command when the TextChanged event is fired. I could copy the interaction line under every textbox but I'm guessing there must be a way to use a template or style to get this working on all of them.
Here is the code for the first textbox
<TextBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" Height="28" HorizontalAlignment="Stretch" Margin="0,12,12,0" Name="TextBox_Description" VerticalAlignment="Top" TabIndex="3" Text="{Binding Item.Description, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding DataChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
If your UpdateMode is always going to be PropertyChanged then there really isn't any reason not to listen to your own PropertyChangedEvent in your ViewModel. It will definitely improve testability.
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
switch (args.PropertyName)
{
case "Prop1":
case "Prop2":
.
.
.
DataChangedCommand.Execute(null);
break;
}
}
I'm not positive how to do it with Interaction.Triggers, but in the past I've used the AttachedCommand dependency properties found here and simply used a Style
Normal XAML
<TextBox local:CommandBehavior.Event="TextChanged"
local:CommandBehavior.Command="{Binding DataChangedCommand}"/>
Using a Style
<Style TargetType="TextBox">
<Setter Property="local:CommandBehavior.Event" Value="TextChanged" />
<Setter Property="local:CommandBehavior.Command" Value="{Binding DataChangedCommand}" />
</Style>
EDIT: Since you can't download files at work, here's the code
CommandBehavior.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Windows;
using System.Windows.Input;
// Code to attach a command to any event
// From http://marlongrech.wordpress.com/2008/12/04/attachedcommandbehavior-aka-acb/
namespace Keys.Controls.Helpers.CommandHelpers
{
/// <summary>
/// Defines the attached properties to create a CommandBehaviorBinding
/// </summary>
public class CommandBehavior
{
#region Behavior
/// <summary>
/// Behavior Attached Dependency Property
/// </summary>
private static readonly DependencyProperty BehaviorProperty =
DependencyProperty.RegisterAttached("Behavior", typeof(CommandBehaviorBinding), typeof(CommandBehavior),
new FrameworkPropertyMetadata((CommandBehaviorBinding)null));
/// <summary>
/// Gets the Behavior property.
/// </summary>
private static CommandBehaviorBinding GetBehavior(DependencyObject d)
{
return (CommandBehaviorBinding)d.GetValue(BehaviorProperty);
}
/// <summary>
/// Sets the Behavior property.
/// </summary>
private static void SetBehavior(DependencyObject d, CommandBehaviorBinding value)
{
d.SetValue(BehaviorProperty, value);
}
#endregion
#region Command
/// <summary>
/// Command Attached Dependency Property
/// </summary>
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CommandBehavior),
new FrameworkPropertyMetadata((ICommand)null,
new PropertyChangedCallback(OnCommandChanged)));
/// <summary>
/// Gets the Command property.
/// </summary>
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
/// <summary>
/// Sets the Command property.
/// </summary>
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
/// <summary>
/// Handles changes to the Command property.
/// </summary>
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandBehaviorBinding binding = FetchOrCreateBinding(d);
binding.Command = (ICommand)e.NewValue;
}
#endregion
#region CommandParameter
/// <summary>
/// CommandParameter Attached Dependency Property
/// </summary>
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CommandBehavior),
new FrameworkPropertyMetadata((object)null,
new PropertyChangedCallback(OnCommandParameterChanged)));
/// <summary>
/// Gets the CommandParameter property.
/// </summary>
public static object GetCommandParameter(DependencyObject d)
{
return (object)d.GetValue(CommandParameterProperty);
}
/// <summary>
/// Sets the CommandParameter property.
/// </summary>
public static void SetCommandParameter(DependencyObject d, object value)
{
d.SetValue(CommandParameterProperty, value);
}
/// <summary>
/// Handles changes to the CommandParameter property.
/// </summary>
private static void OnCommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandBehaviorBinding binding = FetchOrCreateBinding(d);
binding.CommandParameter = e.NewValue;
}
#endregion
#region Event
/// <summary>
/// Event Attached Dependency Property
/// </summary>
public static readonly DependencyProperty EventProperty =
DependencyProperty.RegisterAttached("Event", typeof(string), typeof(CommandBehavior),
new FrameworkPropertyMetadata((string)String.Empty,
new PropertyChangedCallback(OnEventChanged)));
/// <summary>
/// Gets the Event property. This dependency property
/// indicates ....
/// </summary>
public static string GetEvent(DependencyObject d)
{
return (string)d.GetValue(EventProperty);
}
/// <summary>
/// Sets the Event property. This dependency property
/// indicates ....
/// </summary>
public static void SetEvent(DependencyObject d, string value)
{
d.SetValue(EventProperty, value);
}
/// <summary>
/// Handles changes to the Event property.
/// </summary>
private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandBehaviorBinding binding = FetchOrCreateBinding(d);
//check if the Event is set. If yes we need to rebind the Command to the new event and unregister the old one
if (binding.Event != null && binding.Owner != null)
binding.Dispose();
//bind the new event to the command if newValue isn't blank (e.g. switching tabs)
if (e.NewValue.ToString() != "")
binding.BindEvent(d, e.NewValue.ToString());
}
#endregion
#region Helpers
//tries to get a CommandBehaviorBinding from the element. Creates a new instance if there is not one attached
private static CommandBehaviorBinding FetchOrCreateBinding(DependencyObject d)
{
CommandBehaviorBinding binding = CommandBehavior.GetBehavior(d);
if (binding == null)
{
binding = new CommandBehaviorBinding();
CommandBehavior.SetBehavior(d, binding);
}
return binding;
}
#endregion
}
}
CommandBehaviorBinding.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Reflection;
using System.Windows;
// Code to attach a command to any event
// From http://marlongrech.wordpress.com/2008/12/04/attachedcommandbehavior-aka-acb/
namespace Keys.Controls.Helpers.CommandHelpers
{
/// <summary>
/// Defines the command behavior binding
/// </summary>
public class CommandBehaviorBinding : IDisposable
{
#region Properties
/// <summary>
/// Get the owner of the CommandBinding ex: a Button
/// This property can only be set from the BindEvent Method
/// </summary>
public DependencyObject Owner { get; private set; }
/// <summary>
/// The command to execute when the specified event is raised
/// </summary>
public ICommand Command { get; set; }
/// <summary>
/// Gets or sets a CommandParameter
/// </summary>
public object CommandParameter { get; set; }
/// <summary>
/// The event name to hook up to
/// This property can only be set from the BindEvent Method
/// </summary>
public string EventName { get; private set; }
/// <summary>
/// The event info of the event
/// </summary>
public EventInfo Event { get; private set; }
/// <summary>
/// Gets the EventHandler for the binding with the event
/// </summary>
public Delegate EventHandler { get; private set; }
#endregion
//Creates an EventHandler on runtime and registers that handler to the Event specified
public void BindEvent(DependencyObject owner, string eventName)
{
EventName = eventName;
Owner = owner;
Event = Owner.GetType().GetEvent(EventName, BindingFlags.Public | BindingFlags.Instance);
if (Event == null)
throw new InvalidOperationException(String.Format("Could not resolve event name {0}", EventName));
//Create an event handler for the event that will call the ExecuteCommand method
EventHandler = EventHandlerGenerator.CreateDelegate(
Event.EventHandlerType, typeof(CommandBehaviorBinding).GetMethod("ExecuteCommand", BindingFlags.Public | BindingFlags.Instance), this);
//Register the handler to the Event
Event.AddEventHandler(Owner, EventHandler);
}
/// <summary>
/// Executes the command
/// </summary>
public void ExecuteCommand()
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
}
#region IDisposable Members
bool disposed = false;
/// <summary>
/// Unregisters the EventHandler from the Event
/// </summary>
public void Dispose()
{
if (!disposed)
{
Event.RemoveEventHandler(Owner, EventHandler);
disposed = true;
}
}
#endregion
}
}
EventHandlerGenerator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
// Code to attach a command to any event
// From http://marlongrech.wordpress.com/2008/12/04/attachedcommandbehavior-aka-acb/
namespace Keys.Controls.Helpers.CommandHelpers
{
/// <summary>
/// Generates delegates according to the specified signature on runtime
/// </summary>
public static class EventHandlerGenerator
{
/// <summary>
/// Generates a delegate with a matching signature of the supplied eventHandlerType
/// This method only supports Events that have a delegate of type void
/// </summary>
/// <param name="eventInfo">The delegate type to wrap. Note that this must always be a void delegate</param>
/// <param name="methodToInvoke">The method to invoke</param>
/// <param name="methodInvoker">The object where the method resides</param>
/// <returns>Returns a delegate with the same signature as eventHandlerType that calls the methodToInvoke inside</returns>
public static Delegate CreateDelegate(Type eventHandlerType, MethodInfo methodToInvoke, object methodInvoker)
{
//Get the eventHandlerType signature
var eventHandlerInfo = eventHandlerType.GetMethod("Invoke");
Type returnType = eventHandlerInfo.ReturnParameter.ParameterType;
if (returnType != typeof(void))
throw new ApplicationException("Delegate has a return type. This only supprts event handlers that are void");
ParameterInfo[] delegateParameters = eventHandlerInfo.GetParameters();
//Get the list of type of parameters. Please note that we do + 1 because we have to push the object where the method resides i.e methodInvoker parameter
Type[] hookupParameters = new Type[delegateParameters.Length + 1];
hookupParameters[0] = methodInvoker.GetType();
for (int i = 0; i < delegateParameters.Length; i++)
hookupParameters[i + 1] = delegateParameters[i].ParameterType;
DynamicMethod handler = new DynamicMethod("", null,
hookupParameters, typeof(EventHandlerGenerator));
ILGenerator eventIL = handler.GetILGenerator();
//load the parameters or everything will just BAM :)
LocalBuilder local = eventIL.DeclareLocal(typeof(object[]));
eventIL.Emit(OpCodes.Ldc_I4, delegateParameters.Length + 1);
eventIL.Emit(OpCodes.Newarr, typeof(object));
eventIL.Emit(OpCodes.Stloc, local);
//start from 1 because the first item is the instance. Load up all the arguments
for (int i = 1; i < delegateParameters.Length + 1; i++)
{
eventIL.Emit(OpCodes.Ldloc, local);
eventIL.Emit(OpCodes.Ldc_I4, i);
eventIL.Emit(OpCodes.Ldarg, i);
eventIL.Emit(OpCodes.Stelem_Ref);
}
eventIL.Emit(OpCodes.Ldloc, local);
//Load as first argument the instance of the object for the methodToInvoke i.e methodInvoker
eventIL.Emit(OpCodes.Ldarg_0);
//Now that we have it all set up call the actual method that we want to call for the binding
eventIL.EmitCall(OpCodes.Call, methodToInvoke, null);
eventIL.Emit(OpCodes.Pop);
eventIL.Emit(OpCodes.Ret);
//create a delegate from the dynamic method
return handler.CreateDelegate(eventHandlerType, methodInvoker);
}
}
}