I am following James Montemagno's tutorial to add pull-to-refresh support for my Layouts, it works perfect on Android but iOS generates the error below when I navigate to the same page as Android does.
System.InvalidCastException: < Timeout exceeded getting exception details >
The page I am trying to display is a simple StackLayout, which again works perfectly on Android.
This is my iOS renderer class from the tutorial
[assembly: ExportRenderer(typeof(RefreshableLayout), typeof(RefreshableLayoutiOS))]
namespace SocialNetwork.iOS.Renderers
{
[Preserve(AllMembers = true)]
public class RefreshableLayoutiOS : ViewRenderer<RefreshableLayout, UIView>
{
public async static void Init()
{
var temp = DateTime.Now;
}
UIRefreshControl refreshControl;
protected override void OnElementChanged(ElementChangedEventArgs<RefreshableLayout> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
return;
refreshControl = new UIRefreshControl();
refreshControl.ValueChanged += OnRefresh;
try
{
TryInsertRefresh(this);
}
catch (Exception ex)
{
Debug.WriteLine("View is not supported in PullToRefreshLayout: " + ex);
}
UpdateColors();
UpdateIsRefreshing();
UpdateIsSwipeToRefreshEnabled();
}
bool set;
nfloat origininalY;
bool TryOffsetRefresh(UIView view, bool refreshing, int index = 0)
{
if (view is UITableView)
{
var uiTableView = view as UITableView;
if (!set)
{
origininalY = uiTableView.ContentOffset.Y;
set = true;
}
if (refreshing)
uiTableView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY - refreshControl.Frame.Size.Height), true);
else
uiTableView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY), true);
return true;
}
if (view is UICollectionView)
{
var uiCollectionView = view as UICollectionView;
if (!set)
{
origininalY = uiCollectionView.ContentOffset.Y;
set = true;
}
if (refreshing)
uiCollectionView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY - refreshControl.Frame.Size.Height), true);
else
uiCollectionView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY), true);
return true;
}
if (view is UIWebView)
{
//can't do anything
return true;
}
if (view is UIScrollView)
{
var uiScrollView = view as UIScrollView;
if (!set)
{
origininalY = uiScrollView.ContentOffset.Y;
set = true;
}
if (refreshing)
uiScrollView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY - refreshControl.Frame.Size.Height), true);
else
uiScrollView.SetContentOffset(new CoreGraphics.CGPoint(0, origininalY), true);
return true;
}
if (view.Subviews == null)
return false;
for (int i = 0; i < view.Subviews.Length; i++)
{
var control = view.Subviews[i];
if (TryOffsetRefresh(control, refreshing, i))
return true;
}
return false;
}
bool TryInsertRefresh(UIView view, int index = 0)
{
if (view is UITableView)
{
var uiTableView = view as UITableView;
uiTableView = view as UITableView;
view.InsertSubview(refreshControl, index);
return true;
}
if (view is UICollectionView)
{
var uiCollectionView = view as UICollectionView;
uiCollectionView = view as UICollectionView;
view.InsertSubview(refreshControl, index);
return true;
}
if (view is UIWebView)
{
var uiWebView = view as UIWebView;
uiWebView.ScrollView.InsertSubview(refreshControl, index);
return true;
}
if (view is UIScrollView)
{
var uiScrollView = view as UIScrollView;
view.InsertSubview(refreshControl, index);
uiScrollView.AlwaysBounceVertical = true;
return true;
}
if (view.Subviews == null)
return false;
for (int i = 0; i < view.Subviews.Length; i++)
{
var control = view.Subviews[i];
if (TryInsertRefresh(control, i))
return true;
}
return false;
}
BindableProperty rendererProperty;
BindableProperty RendererProperty
{
get
{
if (rendererProperty != null)
return rendererProperty;
var type = Type.GetType("Xamarin.Forms.Platform.iOS.Platform, Xamarin.Forms.Platform.iOS");
var prop = type.GetField("RendererProperty");
var val = prop.GetValue(null);
rendererProperty = val as BindableProperty;
return rendererProperty;
}
}
void UpdateColors()
{
if (RefreshView == null)
return;
if (RefreshView.RefreshColor != Color.Default)
refreshControl.TintColor = RefreshView.RefreshColor.ToUIColor();
if (RefreshView.RefreshBackgroundColor != Color.Default)
refreshControl.BackgroundColor = RefreshView.RefreshBackgroundColor.ToUIColor();
}
void UpdateIsRefreshing()
{
IsRefreshing = RefreshView.IsRefreshing;
}
void UpdateIsSwipeToRefreshEnabled()
{
refreshControl.Enabled = RefreshView.IsPullToRefreshEnabled;
}
public RefreshableLayout RefreshView
{
get { return Element; }
}
bool isRefreshing;
public bool IsRefreshing
{
get { return isRefreshing; }
set
{
bool changed = IsRefreshing != value;
isRefreshing = value;
if (isRefreshing)
refreshControl.BeginRefreshing();
else
refreshControl.EndRefreshing();
if (changed)
TryOffsetRefresh(this, IsRefreshing);
}
}
void OnRefresh(object sender, EventArgs e)
{
if (RefreshView?.RefreshCommand?.CanExecute(RefreshView?.RefreshCommandParameter) ?? false)
{
RefreshView.RefreshCommand.Execute(RefreshView?.RefreshCommandParameter);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == RefreshableLayout.IsPullToRefreshEnabledProperty.PropertyName)
UpdateIsSwipeToRefreshEnabled();
else if (e.PropertyName == RefreshableLayout.IsRefreshingProperty.PropertyName)
UpdateIsRefreshing();
else if (e.PropertyName == RefreshableLayout.RefreshColorProperty.PropertyName)
UpdateColors();
else if (e.PropertyName == RefreshableLayout.RefreshBackgroundColorProperty.PropertyName)
UpdateColors();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (refreshControl != null)
{
refreshControl.ValueChanged -= OnRefresh;
}
}
}
}
I got the code this tutorial and this GitHub
Edit:
XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SocialNetwork.TestScrollPage" xmlns:local="clr-namespace:SocialNetwork.Renderers">
<ContentPage.Content>
<StackLayout>
<local:RefreshableLayout x:Name="RefreshableLayout" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout>
</StackLayout>
</local:RefreshableLayout>
</StackLayout>
</ContentPage.Content>
CS:
public partial class TestScrollPage : ContentPage
{
public TestScrollPage ()
{
InitializeComponent ();
RefreshableLayout.RefreshCommand = new Command(() => RefreshPage());
}
public void RefreshPage()
{
RefreshableLayout.IsRefreshing = false;
DisplayAlert("ok", "ok", "ok");
}
}
And I navigate to the page using Detail = new TestScrollPage();
Edit 2:
public partial class RefreshableLayout : ContentView
{
public static readonly BindableProperty IsRefreshingProperty =
BindableProperty.Create(nameof(IsRefreshing), typeof(bool), typeof(RefreshableLayout), false);
/// <summary>
/// Gets or sets a value indicating whether this instance is refreshing.
/// </summary>
/// <value><c>true</c> if this instance is refreshing; otherwise, <c>false</c>.</value>
public bool IsRefreshing
{
get { return (bool)GetValue(IsRefreshingProperty); }
set
{
if ((bool)GetValue(IsRefreshingProperty) == value)
OnPropertyChanged(nameof(IsRefreshing));
SetValue(IsRefreshingProperty, value);
}
}
/// <summary>
/// The is pull to refresh enabled property.
/// </summary>
public static readonly BindableProperty IsPullToRefreshEnabledProperty =
BindableProperty.Create(nameof(IsPullToRefreshEnabled), typeof(bool), typeof(RefreshableLayout), true);
/// <summary>
/// Gets or sets a value indicating whether this instance is pull to refresh enabled.
/// </summary>
/// <value><c>true</c> if this instance is pull to refresh enabled; otherwise, <c>false</c>.</value>
public bool IsPullToRefreshEnabled
{
get { return (bool)GetValue(IsPullToRefreshEnabledProperty); }
set { SetValue(IsPullToRefreshEnabledProperty, value); }
}
/// <summary>
/// The refresh command property.
/// </summary>
public static readonly BindableProperty RefreshCommandProperty =
BindableProperty.Create(nameof(RefreshCommand), typeof(ICommand), typeof(RefreshableLayout));
/// <summary>
/// Gets or sets the refresh command.
/// </summary>
/// <value>The refresh command.</value>
public ICommand RefreshCommand
{
get { return (ICommand)GetValue(RefreshCommandProperty); }
set { SetValue(RefreshCommandProperty, value); }
}
/// <summary>
/// Gets the Refresh command
/// </summary>
public static readonly BindableProperty RefreshCommandParameterProperty =
BindableProperty.Create(nameof(RefreshCommandParameter),
typeof(object),
typeof(RefreshableLayout),
null,
propertyChanged: (bindable, oldvalue, newvalue) => ((RefreshableLayout)bindable).RefreshCommandCanExecuteChanged(bindable, EventArgs.Empty));
/// <summary>
/// Gets or sets the Refresh command parameter
/// </summary>
public object RefreshCommandParameter
{
get { return GetValue(RefreshCommandParameterProperty); }
set { SetValue(RefreshCommandParameterProperty, value); }
}
/// <summary>
/// Executes if enabled or not based on can execute changed
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
void RefreshCommandCanExecuteChanged(object sender, EventArgs eventArgs)
{
ICommand cmd = RefreshCommand;
if (cmd != null)
IsEnabled = cmd.CanExecute(RefreshCommandParameter);
}
/// <summary>
/// Color property of refresh spinner color
/// </summary>
public static readonly BindableProperty RefreshColorProperty =
BindableProperty.Create(nameof(RefreshColor), typeof(Color), typeof(RefreshableLayout), Color.Default);
/// <summary>
/// Refresh color
/// </summary>
public Color RefreshColor
{
get { return (Color)GetValue(RefreshColorProperty); }
set { SetValue(RefreshColorProperty, value); }
}
/// <summary>
/// Color property of refresh background color
/// </summary>
public static readonly BindableProperty RefreshBackgroundColorProperty =
BindableProperty.Create(nameof(RefreshBackgroundColor), typeof(Color), typeof(RefreshableLayout), Color.Default);
/// <summary>
/// Refresh background color
/// </summary>
public Color RefreshBackgroundColor
{
get { return (Color)GetValue(RefreshBackgroundColorProperty); }
set { SetValue(RefreshBackgroundColorProperty, value); }
}
/// <param name="widthConstraint">The available width for the element to use.</param>
/// <param name="heightConstraint">The available height for the element to use.</param>
/// <summary>
/// Optimization as we can get the size here of our content all in DIP
/// </summary>
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
if (Content == null)
return new SizeRequest(new Size(100, 100));
return base.OnMeasure(widthConstraint, heightConstraint);
}
}
Please read this documentation about Xamarin Liver Player. It declares the limitations:
Custom Renderers are not supported for Xamarin Forms.
Also there are some other limitions or issues when you use Xamarin Liver Player. So I recommend you to use simulators or a real physical device to test your project.
If you don't have a Mac. You can also try to download an Enterprise Visual Studio to let the simulators mapping to Windows.
Related
I want to display a calendar icon, then when the user taps/clicks the calendar icon, the date picker opens up. Is there a way to customize the date picker to implement this functionality?
Do I need a custom handler?
I tried calling Focus() on the date picker, but it didn't open up.
<HorizontalStackLayout>
<DatePicker x:Name="MyDatePicker" MinimumDate="01/01/2022"
MaximumDate="12/31/2022"
Date="06/21/2022" />
<ImageButton Source="calendar.svg" Clicked="ImageButton_Clicked"></ImageButton>
</HorizontalStackLayout>
private void ImageButton_Clicked(object sender, EventArgs e)
{
MyDatePicker.Focus();
}
Note: The icon is from: https://feathericons.com/?query=calendar
Utilizing Maui.FreakyControls and a custom behavior resulted in this solution for Android.
It does require a custom handler to implement successfully.
MauiProgram.cs
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.ConfigureMauiHandlers(handlers =>
{
handlers.AddCustomHandlers();
});
return builder.Build();
}
Shared/Extensions/Extensions.cs
#if ANDROID
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using static Microsoft.Maui.ApplicationModel.Platform;
using NativeImage = Android.Graphics.Bitmap;
#endif
public static void ExecuteCommandIfAvailable(this ICommand command, object parameter = null)
{
if (command?.CanExecute(parameter) == true)
{
command.Execute(parameter);
}
}
public static void AddCustomHandlers(this IMauiHandlersCollection handlers)
{
#if ANDROID
handlers.AddHandler(typeof(Maui.CustomControls.CustomDatePicker), typeof(CustomDatePickerHandler));
#endif
}
#if ANDROID
public static async Task<NativeImage> ToNativeImageSourceAsync(this ImageSource source)
{
var handler = GetHandler(source);
var returnValue = (NativeImage)null;
returnValue = await handler.LoadImageAsync(source, CurrentActivity);
return returnValue;
}
private static IImageSourceHandler GetHandler(this ImageSource source)
{
//Image source handler to return
IImageSourceHandler returnValue = null;
//check the specific source type and return the correct image source handler
switch (source)
{
case UriImageSource:
returnValue = new ImageLoaderSourceHandler();
break;
case FileImageSource:
returnValue = new FileImageSourceHandler();
break;
case StreamImageSource:
returnValue = new StreamImagesourceHandler();
break;
case FontImageSource:
returnValue = new FontImageSourceHandler();
break;
}
return returnValue;
}
#endif
Android/NativeControls/Helpers/DrawableHandlerCallback.cs
public class DrawableHandlerCallback : IDrawableClickListener
{
private readonly IDrawableImageView view;
private readonly Action showPicker;
public DrawableHandlerCallback(IDrawableImageView view, Action showPicker)
{
this.view = view;
this.showPicker = showPicker;
}
public void OnClick(DrawablePosition target)
{
switch (target)
{
case DrawablePosition.Left:
case DrawablePosition.Right:
view.ImageTappedHandler.Invoke(this.showPicker, null);
view.ImageCommand?.ExecuteCommandIfAvailable(view.ImageCommandParameter);
break;
}
}
}
Android/NativeControls/Helpers/DrawablePosition.cs
public enum DrawablePosition
{
Top,
Bottom,
Left,
Right
};
Android/NativeControls/Helpers/IDrawableClickListener.cs
public interface IDrawableClickListener
{
public void OnClick(DrawablePosition target);
}
Android/NativeControls/CustomMauiDatePicker.cs
public class CustomMauiDatePicker : MauiDatePicker
{
private Drawable drawableRight;
private Drawable drawableLeft;
private Drawable drawableTop;
private Drawable drawableBottom;
int actionX, actionY;
private IDrawableClickListener clickListener;
public CustomMauiDatePicker(Context context) : base(context)
{
}
public CustomMauiDatePicker(Context context, IAttributeSet attrs) : base(context, attrs)
{
}
public CustomMauiDatePicker(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
{
}
public override void SetCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
Drawable right, Drawable bottom)
{
if (left != null)
{
drawableLeft = left;
}
if (right != null)
{
drawableRight = right;
}
if (top != null)
{
drawableTop = top;
}
if (bottom != null)
{
drawableBottom = bottom;
}
base.SetCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom);
}
public override bool OnTouchEvent(MotionEvent e)
{
Rect bounds;
if (e.Action == MotionEventActions.Down)
{
actionX = (int)e.GetX();
actionY = (int)e.GetY();
if (drawableBottom != null
&& drawableBottom.Bounds.Contains(actionX, actionY))
{
clickListener.OnClick(DrawablePosition.Bottom);
return base.OnTouchEvent(e);
}
if (drawableTop != null
&& drawableTop.Bounds.Contains(actionX, actionY))
{
clickListener.OnClick(DrawablePosition.Top);
return base.OnTouchEvent(e);
}
// this works for left since container shares 0,0 origin with bounds
if (drawableLeft != null)
{
bounds = null;
bounds = drawableLeft.Bounds;
int x, y;
int extraTapArea = (int)(13 * Resources.DisplayMetrics.Density + 0.5);
x = actionX;
y = actionY;
if (!bounds.Contains(actionX, actionY))
{
// Gives the +20 area for tapping. /
x = (int)(actionX - extraTapArea);
y = (int)(actionY - extraTapArea);
if (x <= 0)
x = actionX;
if (y <= 0)
y = actionY;
// Creates square from the smallest value /
if (x < y)
{
y = x;
}
}
if (bounds.Contains(x, y) && clickListener != null)
{
clickListener.OnClick(DrawablePosition.Left);
e.Action = (MotionEventActions.Cancel);
return false;
}
}
if (drawableRight != null)
{
bounds = null;
bounds = drawableRight.Bounds;
int x, y;
int extraTapArea = 13;
//
// IF USER CLICKS JUST OUT SIDE THE RECTANGLE OF THE DRAWABLE
// THAN ADD X AND SUBTRACT THE Y WITH SOME VALUE SO THAT AFTER
// CALCULATING X AND Y CO-ORDINATE LIES INTO THE DRAWBABLE
// BOUND. - this process help to increase the tappable area of
// the rectangle.
//
x = (int)(actionX + extraTapArea);
y = (int)(actionY - extraTapArea);
//Since this is right drawable subtract the value of x from the width
// of view. so that width - tappedarea will result in x co-ordinate in drawable bound.
//
x = Width - x;
//x can be negative if user taps at x co-ordinate just near the width.
// e.g views width = 300 and user taps 290. Then as per previous calculation
// 290 + 13 = 303. So subtract X from getWidth() will result in negative value.
// So to avoid this add the value previous added when x goes negative.
//
if (x <= 0)
{
x += extraTapArea;
}
// If result after calculating for extra tappable area is negative.
// assign the original value so that after subtracting
// extratapping area value doesn't go into negative value.
//
if (y <= 0)
y = actionY;
//If drawble bounds contains the x and y points then move ahead./
if (bounds.Contains(x, y) && clickListener != null)
{
clickListener
.OnClick(DrawablePosition.Right);
e.Action = (MotionEventActions.Cancel);
return false;
}
return base.OnTouchEvent(e);
}
}
return base.OnTouchEvent(e);
}
protected override void JavaFinalize()
{
drawableRight = null;
drawableBottom = null;
drawableLeft = null;
drawableTop = null;
base.JavaFinalize();
}
public void SetDrawableClickListener(IDrawableClickListener listener)
{
this.clickListener = listener;
}
}
Platforms/Android/CustomDatePickerHandler.android.cs
public partial class CustomDatePickerHandler
{
DatePickerDialog? _dialog;
protected override MauiDatePicker CreatePlatformView()
{
var mauiDatePicker = new CustomMauiDatePicker(Context)
{
ShowPicker = ShowPickerDialog,
HidePicker = HidePickerDialog
};
var date = VirtualView?.Date;
if (date != null)
_dialog = CreateDatePickerDialog(date.Value.Year, date.Value.Month, date.Value.Day);
var colorStateList = ColorStateList.ValueOf(Android.Graphics.Color.Transparent);
ViewCompat.SetBackgroundTintList(mauiDatePicker, colorStateList);
return mauiDatePicker;
}
protected override void DisconnectHandler(MauiDatePicker platformView)
{
base.DisconnectHandler(platformView);
if (_dialog != null)
{
_dialog.Hide();
_dialog.Dispose();
_dialog = null;
}
}
internal DatePickerDialog? DatePickerDialog { get { return _dialog; } }
internal async Task HandleAndAlignImageSourceAsync(Maui.CustomControls.CustomDatePicker entry)
{
var imageBitmap = await entry.ImageSource?.ToNativeImageSourceAsync();
if (imageBitmap != null)
{
var bitmapDrawable = new BitmapDrawable(Platform.CurrentActivity?.Resources,
Bitmap.CreateScaledBitmap(imageBitmap, entry.ImageWidth * 2, entry.ImageHeight * 2, true));
var customDatePicker = PlatformView as CustomMauiDatePicker;
customDatePicker.SetDrawableClickListener(new DrawableHandlerCallback(entry, customDatePicker.ShowPicker));
switch (entry.ImageAlignment)
{
case ImageAlignment.Left:
customDatePicker.SetCompoundDrawablesWithIntrinsicBounds(bitmapDrawable, null, null, null);
break;
case ImageAlignment.Right:
customDatePicker.SetCompoundDrawablesWithIntrinsicBounds(null, null, bitmapDrawable, null);
break;
}
}
PlatformView.CompoundDrawablePadding = entry.ImagePadding;
}
void ShowPickerDialog()
{
if (VirtualView == null)
return;
if (_dialog != null && _dialog.IsShowing)
return;
var date = VirtualView.Date;
ShowPickerDialog(date.Year, date.Month - 1, date.Day);
}
void ShowPickerDialog(int year, int month, int day)
{
if (_dialog == null)
_dialog = CreateDatePickerDialog(year, month, day);
else
{
EventHandler? setDateLater = null;
setDateLater = (sender, e) => { _dialog!.UpdateDate(year, month, day); _dialog.ShowEvent -= setDateLater; };
_dialog.ShowEvent += setDateLater;
}
_dialog.Show();
}
void HidePickerDialog()
{
_dialog?.Hide();
}
}
Shared/Controls/IDrawableImageView.cs
public interface IDrawableImageView
{
public EventHandler ImageTappedHandler
{
get;
}
public object ImageCommandParameter
{
get;
}
public ICommand ImageCommand
{
get;
}
public int ImagePadding
{
get;
}
public int ImageWidth
{
get;
}
public int ImageHeight
{
get;
}
public ImageSource ImageSource
{
get;
}
public ImageAlignment ImageAlignment
{
get;
}
}
Shared/CustomDatePicker/CustomDatePicker.cs
public class CustomDatePicker : DatePicker, IDrawableImageView
{
private EventHandler imageTapped;
public EventHandler ImageTappedHandler
{
get { return imageTapped; }
}
public event EventHandler ImageTapped
{
add
{
imageTapped += value;
}
remove
{
imageTapped -= value;
}
}
public static readonly BindableProperty ImageSourceProperty = BindableProperty.Create(
nameof(Image),
typeof(ImageSource),
typeof(CustomDatePicker),
default(ImageSource));
public static readonly BindableProperty ImageHeightProperty = BindableProperty.Create(
nameof(ImageHeight),
typeof(int),
typeof(CustomDatePicker),
25);
public static readonly BindableProperty ImageWidthProperty = BindableProperty.Create(
nameof(ImageWidth),
typeof(int),
typeof(CustomDatePicker),
25);
public static readonly BindableProperty ImageAlignmentProperty = BindableProperty.Create(
nameof(ImageAlignment),
typeof(ImageAlignment),
typeof(CustomDatePicker),
ImageAlignment.Right);
public static readonly BindableProperty ImagePaddingProperty = BindableProperty.Create(
nameof(ImagePadding),
typeof(int),
typeof(CustomDatePicker),
5);
public static readonly BindableProperty ImageCommandProperty = BindableProperty.Create(
nameof(ImagePadding),
typeof(ICommand),
typeof(CustomDatePicker),
default(ICommand));
public static readonly BindableProperty ImageCommandParameterProperty = BindableProperty.Create(
nameof(ImageCommandParameter),
typeof(object),
typeof(CustomDatePicker),
default(object));
/// <summary>
/// Command parameter for your Image tap command
/// </summary>
public object ImageCommandParameter
{
get => GetValue(ImageCommandParameterProperty);
set => SetValue(ImageCommandParameterProperty, value);
}
/// <summary>
/// <see cref="ImageCommand"/> of type <see cref="ICommand"/> that you can use to bind with your Image that you added to your control's ViewPort
/// </summary>
public ICommand ImageCommand
{
get => (ICommand)GetValue(ImageCommandProperty);
set => SetValue(ImageCommandProperty, value);
}
/// <summary>
/// Padding of the Image as <see cref="int"/> that you added to the ViewPort
/// </summary>
public int ImagePadding
{
get => (int)GetValue(ImagePaddingProperty);
set => SetValue(ImagePaddingProperty, value);
}
/// <summary>
/// Width of the Image in your ViewPort
/// </summary>
public int ImageWidth
{
get => (int)GetValue(ImageWidthProperty);
set => SetValue(ImageWidthProperty, value);
}
/// <summary>
/// Height of the Image in your ViewPort
/// </summary>
public int ImageHeight
{
get => (int)GetValue(ImageHeightProperty);
set => SetValue(ImageHeightProperty, value);
}
/// <summary>
/// An <see cref="ImageSource"/> that you want to add to your ViewPort
/// </summary>
public ImageSource ImageSource
{
get => (ImageSource)GetValue(ImageSourceProperty);
set => SetValue(ImageSourceProperty, value);
}
/// <summary>
/// <see cref="ImageAlignment"/> for your Image's ViewPort, By default set to Right.
/// </summary>
public ImageAlignment ImageAlignment
{
get => (ImageAlignment)GetValue(ImageAlignmentProperty);
set => SetValue(ImageAlignmentProperty, value);
}
}
Shared/CustomDatePickerHandler/CustomDatePickerHandler.cs
#if ANDROID
public partial class CustomDatePickerHandler : DatePickerHandler
{
public CustomDatePickerHandler()
{
Mapper.AppendToMapping("CustomDatePickerCustomization", MapDatePicker);
}
private void MapDatePicker(IDatePickerHandler datePickerHandler, IDatePicker datePicker)
{
if (datePicker is Maui.CustomControls.CustomDatePicker customDatePicker &&
datePickerHandler is CustomDatePickerHandler customDatePickerHandler)
{
if (customDatePicker.ImageSource != default(ImageSource))
{
customDatePickerHandler.HandleAndAlignImageSourceAsync(customDatePicker).RunConcurrently();
}
}
}
}
#endif
Shared/Enums/ImageAlignment.cs
public enum ImageAlignment
{
/// <summary>
/// Aligns your control to the left view port of the view.
/// </summary>
Left,
/// <summary>
/// Aligns your control to the right view port of the view.
/// </summary>
Right
}
Shared/Extensions/TaskExtensions.cs
public static class TaskExtensions
{
public static void RunConcurrently(this Task task)
{
if (task == null)
throw new ArgumentNullException("task", "task is null.");
if (task.Status == TaskStatus.Created)
task.Start();
}
}
CustomDatePickerPage.xaml
<StackLayout Padding="10,60,10,0">
<Label Text="Please enter a date."
FontSize="12" />
<custom:CustomDatePicker MinimumDate="01/01/2022"
MaximumDate="12/31/2022"
Date="06/21/2022"
ImageSource="calendar"
ImageAlignment="Right"
ImageHeight="40"
ImageWidth="40"
ImagePadding="10"
FontSize="Large">
<custom:CustomDatePicker.Behaviors>
<local:CustomDatePickerBehavior />
</custom:CustomDatePicker.Behaviors>
</custom:CustomDatePicker>
</StackLayout>
Behaviors/CustomDatePickerBehavior.cs
public class CustomDatePickerBehavior : Behavior<CustomDatePicker>
{
protected override void OnAttachedTo(CustomDatePicker picker)
{
picker.ImageTapped += Picker_ImageTapped;
base.OnAttachedTo(picker);
}
protected override void OnDetachingFrom(CustomDatePicker picker)
{
picker.ImageTapped -= Picker_ImageTapped;
base.OnDetachingFrom(picker);
}
private void Picker_ImageTapped(object sender, EventArgs e)
{
Action showPicker = (Action)sender;
showPicker.Invoke();
}
}
I have requirement to show badge notification icon on NavigationBar right side
for which I have used below code in my native Xamari.iOS project but now I wanted it to use in Xamarin.Forms as Custom Control.
Could someone help me to create Renderer for iOS and Android this type of Custom Controls in Xamarin.Forms?
using System;
using UIKit;
using CoreGraphics;
using CoreAnimation;
using Foundation;
namespace App1.iOS
{
public class BadgeBarButtonItem : UIBarButtonItem
{
#region Fields
UILabel _badge;
string _badgeValue;
UIColor _badgeBGColor;
UIColor _badgeTextColor;
UIColor _badgeBorderColor;
UIFont _badgeFont;
nfloat _badgePadding;
nfloat _badgeMinSize;
nfloat _badgeOriginX;
nfloat _badgeOriginY;
nfloat _badgeBorderWidth;
bool _shouldHideBadgeAtZero;
bool _shouldAnimateBadge;
#endregion
#region Public properties
// Each time you change one of the properties, the badge will refresh with your changes
/// <summary>
/// Gets or sets the badge value to be displayed.
/// </summary>
/// <value>The badge value.</value>
public string BadgeValue
{
get
{
return _badgeValue;
}
set
{
_badgeValue = value;
SetBadgeValue(value);
}
}
/// <summary>
/// Gets or sets the badge background color.
/// </summary>
/// <value>The color of the badge background.</value>
public UIColor BadgeBGColor
{
get
{
return _badgeBGColor;
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(BadgeBGColor));
_badgeBGColor = value;
if (_badge != null)
{
this.RefreshBadge();
}
}
}
/// <summary>
/// Gets or sets the badge text color.
/// </summary>
/// <value>The color of the badge text.</value>
public UIColor BadgeTextColor
{
get
{
return _badgeTextColor;
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(BadgeTextColor));
_badgeTextColor = value;
if (_badge != null)
{
this.RefreshBadge();
}
}
}
/// <summary>
/// Gets or sets the badge border color.
/// </summary>
/// <value>The color of the badge border.</value>
public UIColor BadgeBorderColor
{
get
{
return _badgeBorderColor;
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(BadgeBorderColor));
_badgeBorderColor = value;
if (_badge != null)
{
this.RefreshBadge();
}
}
}
/// <summary>
/// Gets or sets the badge font.
/// </summary>
/// <value>The badge font.</value>
public UIFont BadgeFont
{
get
{
return _badgeFont;
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(BadgeFont));
_badgeFont = value;
if (_badge != null)
{
this.RefreshBadge();
}
}
}
/// <summary>
/// Gets or sets the padding value for the badge.
/// </summary>
/// <value>The badge padding.</value>
public nfloat BadgePadding
{
get
{
return _badgePadding;
}
set
{
_badgePadding = value;
if (_badge != null)
{
this.UpdateBadgeFrame();
}
}
}
/// <summary>
/// Gets or sets the minimum size of the badge minimum.
/// </summary>
/// <value>The minimum size of the badge.</value>
public nfloat BadgeMinSize
{
get
{
return _badgeMinSize;
}
set
{
_badgeMinSize = value;
if (_badge != null)
{
this.UpdateBadgeFrame();
}
}
}
/// <summary>
/// Gets or sets the X value for offsetting the badge over the BarButtonItem.
/// </summary>
/// <value>The badge X origin.</value>
public nfloat BadgeOriginX
{
get
{
return _badgeOriginX;
}
set
{
_badgeOriginX = value;
if (_badge != null)
{
this.UpdateBadgeFrame();
}
}
}
/// <summary>
/// Gets or sets the Y value for offsetting the badge over the BarButtonItem.
/// </summary>
/// <value>The badge Y origin.</value>
public nfloat BadgeOriginY
{
get
{
return _badgeOriginY;
}
set
{
_badgeOriginY = value;
if (_badge != null)
{
this.UpdateBadgeFrame();
}
}
}
/// <summary>
/// Gets or sets the width of the badge border.
/// </summary>
/// <value>The badge border width.</value>
public nfloat BadgeBorderWidth
{
get
{
return _badgeBorderWidth;
}
set
{
_badgeBorderWidth = value;
if (_badge != null)
{
this.RefreshBadge();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="Utility.BadgeBarButtonItem"/> should remove the badge when reaching zero.
/// </summary>
/// <value><c>true</c> if should hide badge at zero; otherwise, <c>false</c>.</value>
public bool ShouldHideBadgeAtZero
{
get
{
return _shouldHideBadgeAtZero;
}
set
{
_shouldHideBadgeAtZero = value;
if (_badge != null)
{
this.UpdateBadgeFrame();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="Utility.BadgeBarButtonItem"/> should animate the badge when its value changes.
/// </summary>
/// <value><c>true</c> if should animate badge; otherwise, <c>false</c>.</value>
public bool ShouldAnimateBadge
{
get
{
return _shouldAnimateBadge;
}
set
{
_shouldAnimateBadge = value;
if (_badge != null)
{
this.UpdateBadgeFrame();
}
}
}
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="Utility.BadgeBarButtonItem"/> class.
/// </summary>
/// <param name="customButton">Custom button.</param>
public BadgeBarButtonItem(UIButton customButton)
{
this.CustomView = customButton;
if (this.CustomView != null)
{
Initializer();
}
}
protected virtual void Initializer()
{
// Default design initialization
this.BadgeBGColor = UIColor.Red;
this.BadgeBorderColor = UIColor.White;
this.BadgeTextColor = UIColor.White;
this.BadgeFont = UIFont.SystemFontOfSize(12);
this.BadgePadding = 6;
this.BadgeMinSize = 8;
this.BadgeOriginX = 7;
this.BadgeOriginY = -9;
this.BadgeBorderWidth = 0;
this.ShouldHideBadgeAtZero = true;
this.ShouldAnimateBadge = true;
// Avoids badge to be clipped when animating its scale
this.CustomView.ClipsToBounds = false;
}
#region Utility methods
// Handles badge display when its properties have been changed (background, color and font)
void RefreshBadge()
{
// Change new attributes
_badge.TextColor = this.BadgeTextColor;
_badge.BackgroundColor = this.BadgeBGColor;
_badge.Font = this.BadgeFont;
SetBorder();
}
void UpdateBadgeFrame()
{
// When the value changes the badge could need to get bigger
// Calculate expected size to fit new value
// Use an intermediate label to get expected size thanks to sizeToFit
// We don't call sizeToFit on the true label to avoid bad display
UILabel frameLabel = DuplicateLabel(_badge);
frameLabel.SizeToFit();
CGSize expectedLabelSize = frameLabel.Frame.Size;
// Make sure that for small value, the badge will be big enough
nfloat minHeight = expectedLabelSize.Height;
// Using a const we make sure the badge respect the minimum size
minHeight = (minHeight < this.BadgeMinSize) ? this.BadgeMinSize : expectedLabelSize.Height;
nfloat minWidth = expectedLabelSize.Width;
nfloat padding = this.BadgePadding;
// Using const we make sure the badge doesn't get too smal
minWidth = (minWidth < minHeight) ? minHeight : expectedLabelSize.Width;
_badge.Frame = new CGRect(this.BadgeOriginX, this.BadgeOriginY, minWidth + padding, minHeight + padding);
_badge.Layer.CornerRadius = (minHeight + padding) / 2;
_badge.Layer.MasksToBounds = true;
}
// Handle the badge changing value
void UpdateBadgeValueAnimated(bool animated)
{
// Bounce animation on badge if value changed and if animation authorized
if (animated && this.ShouldAnimateBadge && _badge.Text != this.BadgeValue)
{
var animation = new CABasicAnimation();
animation.KeyPath = #"transform.scale";
animation.From = NSObject.FromObject(1.5);
animation.To = NSObject.FromObject(1);
animation.Duration = 0.2;
animation.TimingFunction = new CAMediaTimingFunction(0.4f, 1.3f, 1f, 1f);
_badge.Layer.AddAnimation(animation, #"bounceAnimation");
}
// Set the new value
_badge.Text = this.BadgeValue;
// Animate the size modification if needed
double duration = animated ? 0.2 : 0;
UIView.Animate(duration, UpdateBadgeFrame);
}
static UILabel DuplicateLabel(UILabel labelToCopy)
{
var duplicateLabel = new UILabel(labelToCopy.Frame);
duplicateLabel.Text = labelToCopy.Text;
duplicateLabel.Font = labelToCopy.Font;
return duplicateLabel;
}
void RemoveBadge()
{
if (_badge != null)
{
// Animate badge removal
UIView.AnimateNotify(0.15f, 0.0F,
UIViewAnimationOptions.CurveEaseIn,
() => {
_badge.Transform = CGAffineTransform.MakeScale(0.1f, 0.1f);
},
completed => {
_badge.RemoveFromSuperview();
_badge = null;
}
);
}
}
#endregion
#region Setter
void SetBadgeValue(string badgeValue)
{
// Set new value
_badgeValue = badgeValue;
// When changing the badge value check if we need to remove the badge
if (string.IsNullOrEmpty(badgeValue) || (badgeValue == #"0" && this.ShouldHideBadgeAtZero))
{
RemoveBadge();
}
else if (_badge == null)
{
// Create a new badge because it doesn't exist
_badge = new UILabel(new CGRect(this.BadgeOriginX, this.BadgeOriginY, 20, 20))
{
TextColor = this.BadgeTextColor,
BackgroundColor = this.BadgeBGColor,
Font = this.BadgeFont,
TextAlignment = UITextAlignment.Center,
};
// Fix for iOS 9: Correctly apply the CornerRadius later on
_badge.Layer.MasksToBounds = true;
SetBorder();
this.CustomView.AddSubview(_badge);
this.UpdateBadgeValueAnimated(false);
}
else
{
this.UpdateBadgeValueAnimated(true);
}
}
void SetBorder()
{
if (this.BadgeBorderWidth > 0)
{
_badge.Layer.BorderColor = BadgeBorderColor.CGColor;
}
_badge.Layer.BorderWidth = this.BadgeBorderWidth;
}
#endregion
}
}
I got this control from below link please refer it:-
https://github.com/fabiogaragiola/BadgeBarButtonItem
You should create custom renderer for page, then in OnElementChange you can call this.NavigationItem.RightBarButtonItem = barButton; to add your custom toolbar button.
i'm getting an error with this code new RoutedEventHandler(Notifications.showNotifications(window)); saying:
An object reference is required for the non-static field, method, or
property 'Notifications.showNotifications(Window)'
Also, can I get an example of how I could dump out each Dictionary object from within my _notifications object in the runtimeObject class? I need to do this when showNotifications is called!
When I say dump out, based on the test notifications which I try to add in the code, I just want to display a MessageBox to show that they are being added, so using the code above the MessageBox would show:
MessageBox
Error Code: 1, Text: Error 1
Error Code: 2, Text: Normal Message
Error Code: 3, Text: Tip
App.xaml.cs
namespace Test_Project
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
//Startup
Window main = new MainWindow();
main.Show();
//Attach Event Handlers to MainWindow
if (!attachEventHandlers(main))
{
MessageBox.Show("Fatal Error: Unable to attach event handlers, contact administrator!",
"Fatal Error!",
MessageBoxButton.OK,
MessageBoxImage.Exclamation);
}
}
public bool attachEventHandlers(Window window)
{
//window.MouseMove += new RoutedEventHandler(Notifications.showNotifications(window));
return true;
}
}
public class Notifications
{
/// <summary>
/// Show the notifications
/// </summary>
/// <param name="window"></param>
public void showNotifications(Window window)
{
// Find the resource, then cast it to a runtimeObject
var runtime = (runtimeObject)Application.Current.TryFindResource("runtimeVariables");
//Create messagebox with all the notifications in to test
}
public bool addNotifications()
{
// Find the resource, then cast it to a runtimeObject
var runtime = (runtimeObject)Application.Current.TryFindResource("runtimeVariables");
//Create Dictionary
Dictionary<int, string> arr = new Dictionary<int, string>();
arr.Add(1, "Error 1");
arr.Add(2, "Normal Message");
arr.Add(3, "Tip");
//Create test notifications
runtime.notifications.Add(arr);
return true;
}
}
/// <summary>
/// Global values for use during application runtime
/// </summary>
public class runtimeObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
//Can the application be closed?
private bool _inProgress = false;
public bool inProgress
{
get { return _inProgress; }
set
{
if (_inProgress != value)
{
_inProgress = value;
OnPropertyChanged("inProgress");
}
}
}
/// <summary>
/// Notifications held
/// Array((int)type,(string)"message")
/// </summary>
private List<Dictionary<int, string>> _notifications;
public List<Dictionary<int, string>> notifications
{
get { return _notifications; }
set
{
if (_notifications != value)
{
_notifications = value;
OnPropertyChanged("notifications");
}
}
}
//Selected folder to search in
private int _uploadProgress = 0;
public int uploadProgress
{
get { return _uploadProgress; }
set
{
if (_uploadProgress != value)
{
//int Angle = (_uploadProgress * 360) / 100;
//Classes.CircularProgress.RenderArc(Angle);
_uploadProgress = value;
OnPropertyChanged("uploadProgress");
}
}
}
}
}
I am working with a custom control which has a selected items dependency property which I have wired up the collection changed event but the UI is not notified and the PropertyChanged event is always null. Normally I would say this is a datacontext issue. but I cannot change the data context on the control as no data will display.
public ObservableCollection<object> SelectedItems
{
get { return (ObservableCollection<object>)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedItems. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.RegisterAttached("SelectedItems", typeof(ObservableCollection<object>),
typeof(MultiSelectComboBox),
new System.Windows.PropertyMetadata(new ObservableCollection<object>(), new PropertyChangedCallback(SelectedItemsPropertyChanged)));
private static void SelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MultiSelectComboBox relay = d as MultiSelectComboBox;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= relay.SelectedItemsCollectionChanged;
}
if (e.NewValue != null)
{
var coll = (INotifyCollectionChanged)e.NewValue;
coll.CollectionChanged += relay.SelectedItemsCollectionChanged;
}
}
Above is the property declaration it is bound in xaml to an ObservableCollection on the ViewModel. What am I missing. The control implements INotifyPropertyChanged.
I have added more code below. This is the original code and I would like to change the same property to a dependency property for binding to collection puposes.
namespace Kepler.SilverlightControls.MultiSelectComboBox
{
/// <summary>
/// MultiSelect ComboBox
/// </summary>
public class MultiSelectComboBox : Telerik.Windows.Controls.RadComboBox, INotifyPropertyChanged
{
#region Events
/// <summary>
/// Est appelé quand une propriété change
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of MultiSelectComboBox
/// </summary>
public MultiSelectComboBox()
{
ClearSelectionButtonVisibility = Visibility.Collapsed;
string xaml = #"<DataTemplate
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:local=""clr-namespace:Kepler.SilverlightControls.MultiSelectComboBox;assembly=Kepler.SilverlightControls"">
<TextBlock TextWrapping=""Wrap"" local:MultiSelectComboBoxService.SelectionBoxLoaded=""True"" />
</DataTemplate>";
var selectionBoxTemplate = (DataTemplate)XamlReader.Load(xaml);
SelectionBoxTemplate = selectionBoxTemplate;
EmptySelectionBoxTemplate = selectionBoxTemplate;
xaml = #"<DataTemplate
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:local=""clr-namespace:Kepler.SilverlightControls.MultiSelectComboBox;assembly=Kepler.SilverlightControls"">
<CheckBox local:MultiSelectComboBoxService.ComboBoxItemLoaded=""True""
IsChecked=""{Binding Path=(local:MultiSelectComboBoxService.IsChecked), Mode=TwoWay, RelativeSource={RelativeSource Self}}"" />
</DataTemplate>";
ItemTemplate = (DataTemplate)XamlReader.Load(xaml);
}
#endregion
#region Propriétés
/// <summary>
/// IsCheckedBindingPath Property
/// </summary>
public string IsCheckedBindingPath
{
get { return (string)GetValue(IsCheckedBindingPathProperty); }
set { SetValue(IsCheckedBindingPathProperty, value); }
}
// Using a DependencyProperty as the backing store for IsCheckedBindingPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCheckedBindingPathProperty =
DependencyProperty.Register("IsCheckedBindingPath", typeof(string), typeof(MultiSelectComboBox), new PropertyMetadata(null, (obj, e) =>
{
Telerik.Windows.Controls.TextSearch.SetTextPath(obj, e.NewValue as string);
}));
/// <summary>
/// DisplayBindingPath Property
/// </summary>
public static readonly DependencyProperty DisplayBindingPathProperty = DependencyProperty.Register(
"DisplayBindingPath", typeof(string), typeof(MultiSelectComboBox), new PropertyMetadata(null, (obj, e) =>
{
Telerik.Windows.Controls.TextSearch.SetTextPath(obj, e.NewValue as string);
}));
/// <summary>
/// Gets or sets the display member path (we can't reuse DisplayMemberPath property)
/// </summary>
public string DisplayBindingPath
{
get { return GetValue(DisplayBindingPathProperty) as string; }
set { SetValue(DisplayBindingPathProperty, value); }
}
private ObservableCollection<object> _selectedItems;
/// <summary>
/// Gets the selected items
/// </summary>
public ObservableCollection<object> SelectedItems
{
get
{
if (_selectedItems == null)
{
_selectedItems = new ObservableCollection<object>();
_selectedItems.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedItemsCollectionChanged);
}
return _selectedItems;
}
}
private ObservableCollection<object> _selectedValues;
/// <summary>
/// Gets the selected values
/// </summary>
public ObservableCollection<object> SelectedValues
{
get
{
if (_selectedValues == null)
{
_selectedValues = new ObservableCollection<object>();
_selectedValues.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedValuesCollectionChanged);
}
return _selectedValues;
}
}
#endregion
#region Methods
/// <summary>
/// Called when the Items property changed
/// </summary>
/// <param name="e">change informations</param>
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
int idx;
var selectedItems = SelectedItems;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Reset:
var items = e.NewItems;
if (items == null)
{
var selected = new List<object>();
foreach (var item in this.ItemsSource)
{
PropertyInfo isCheckedBindingPathProperty = this.IsCheckedBindingPath != null ? item.GetType().GetProperty(this.IsCheckedBindingPath) : null;
if (isCheckedBindingPathProperty != null
&& (bool)isCheckedBindingPathProperty.GetValue(item,null) == true)
{
selected.Add(item);
SelectedValues.Add(item.GetType().GetProperty(SelectedValuePath).GetValue(item, null));
}
}
items = selected;
}
if (items != null)
{
foreach (object value in SelectedValues)
{
foreach (object item in items)
{
if (GetSelectedValue(item).Equals(value) && !selectedItems.Contains(item))
{
selectedItems.Add(item);
}
}
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (object item in e.OldItems)
{
idx = selectedItems.IndexOf(item);
if (idx >= 0)
{
selectedItems.RemoveAt(idx);
}
}
break;
}
}
private void RemoveCollectionChangedEvents()
{
SelectedItems.CollectionChanged -= new NotifyCollectionChangedEventHandler(SelectedItemsCollectionChanged);
SelectedValues.CollectionChanged -= new NotifyCollectionChangedEventHandler(SelectedValuesCollectionChanged);
}
private void AddCollectionChangedEvents()
{
SelectedItems.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedItemsCollectionChanged);
SelectedValues.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedValuesCollectionChanged);
}
private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (SelectedValuePath != null)
{
RemoveCollectionChangedEvents();
try
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddSelectedValues(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveSelectedValues(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
RemoveSelectedValues(e.OldItems);
AddSelectedValues(e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
SelectedValues.Clear();
foreach (object item in Items)
{
UpdateSelectedItem(item, false);
}
AddSelectedValues(e.NewItems);
break;
}
}
finally
{
AddCollectionChangedEvents();
}
}
RaiseSelectedItemsPropertyChanged();
}
private void RemoveSelectedValues(IList items)
{
foreach (var item in items)
{
SelectedValues.Remove(GetSelectedValue(item));
UpdateSelectedItem(item, false);
}
}
private void AddSelectedValues(IList items)
{
if (items != null)
{
object selectedValue;
foreach (var item in items)
{
selectedValue = GetSelectedValue(item);
if (!SelectedValues.Contains(selectedValue))
{
SelectedValues.Add(selectedValue);
}
UpdateSelectedItem(item, true);
}
}
}
private object GetSelectedValue(object item)
{
return DataControlHelper.GetPropertyInfo(item.GetType(), SelectedValuePath).GetValue(item, null);
}
private void SelectedValuesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RemoveCollectionChangedEvents();
try
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddSelectedItems(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveSelectedItems(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
RemoveSelectedItems(e.OldItems);
AddSelectedItems(e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
var selectedItems = SelectedItems.ToList();
SelectedItems.Clear();
foreach (object item in selectedItems)
{
UpdateSelectedItem(item, false);
}
AddSelectedItems(e.NewItems);
break;
}
}
finally
{
AddCollectionChangedEvents();
}
RaiseSelectedItemsPropertyChanged();
}
private void RaiseSelectedItemsPropertyChanged()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItems"));
// To update the selection box
}
}
private void RemoveSelectedItems(IList values)
{
object item;
foreach (var value in values)
{
item = SelectedItems.FirstOrDefault(e => GetSelectedValue(e).Equals(value));
if (item != null)
{
SelectedItems.Remove(item);
UpdateSelectedItem(item, false);
}
}
}
private void AddSelectedItems(IList values)
{
if (values != null)
{
object item;
foreach (var value in values)
{
item = Items.FirstOrDefault(e => GetSelectedValue(e).Equals(value));
if (item != null)
{
SelectedItems.Add(item);
UpdateSelectedItem(item, true);
}
}
}
}
private void UpdateSelectedItem(object item, bool select)
{
var obj = ItemContainerGenerator.ContainerFromItem(item);
if (obj != null)
{
var cb = obj.FindChildByType<CheckBox>();
if (cb != null && cb.IsChecked != select)
{
cb.IsChecked = select;
}
}
}
/// <summary>
/// Create a new ComboBox item
/// </summary>
/// <returns>a new ComboBox item</returns>
protected override DependencyObject GetContainerForItemOverride()
{
return new MultiSelectComboBoxItem(this);
}
protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
{
base.OnKeyDown(e);
IsDropDownOpen = true;
}
#endregion
}
}
This service class is below:
namespace Kepler.SilverlightControls.MultiSelectComboBox
{
/// <summary>
/// Service for the MultiSelect comboBox
/// </summary>
public static class MultiSelectComboBoxService
{
/// <summary>
/// IsChecked property
/// </summary>
public static DependencyProperty IsCheckedProperty = DependencyProperty.RegisterAttached("IsChecked",
typeof(bool), typeof(MultiSelectComboBoxService), new PropertyMetadata(false, (obj, e) =>
{
MultiSelectComboBoxItem comboBoxItem = obj.GetVisualParent<MultiSelectComboBoxItem>();
if (comboBoxItem != null)
{
MultiSelectComboBox comboBox = comboBoxItem.ParentComboBox;
var selectedItems = (IList)comboBox.SelectedItems;
object item = comboBoxItem.DataContext;
PropertyInfo isCheckedBindingPathProperty = item.GetType().GetProperty(comboBox.IsCheckedBindingPath);
isCheckedBindingPathProperty.SetValue(item, e.NewValue,null);
if ((bool)e.NewValue)
{
if (!selectedItems.Contains(item))
{
selectedItems.Add(item);
}
}
else
{
selectedItems.Remove(item);
}
}
}));
/// <summary>
/// Gets a value indicating if the object is checked or not
/// </summary>
/// <param name="obj">DependencyObject</param>
/// <returns>a value indicating if the object is checked or not</returns>
public static bool GetIsChecked(DependencyObject obj)
{
return (bool)obj.GetValue(IsCheckedProperty);
}
/// <summary>
/// Sets a value indicating if the object is checked or not
/// </summary>
/// <param name="obj">DependencyObject</param>
/// <param name="value">the value indicating if the object is checked or not</param>
public static void SetIsChecked(DependencyObject obj, bool value)
{
obj.SetValue(IsCheckedProperty, value);
}
/// <summary>
/// SelectionBoxLoaded property called on SelectionBox load
/// </summary>
public static DependencyProperty SelectionBoxLoadedProperty = DependencyProperty.RegisterAttached("SelectionBoxLoaded",
typeof(bool), typeof(MultiSelectComboBoxService), new PropertyMetadata(false, (obj, e) =>
{
TextBlock targetElement = obj as TextBlock;
if (targetElement != null)
{
targetElement.Loaded += new RoutedEventHandler(targetElement_Loaded);
}
}));
private static void targetElement_Loaded(object sender, RoutedEventArgs e)
{
TextBlock targetElement = (TextBlock)sender;
targetElement.Loaded -= new RoutedEventHandler(targetElement_Loaded);
MultiSelectComboBox comboBox = targetElement.GetVisualParent<MultiSelectComboBox>();
if (comboBox != null)
{
targetElement.SetBinding(TextBlock.TextProperty, new Binding("SelectedItems")
{
Converter = new MultiSelectComboxConverter(),
Source = comboBox,
ConverterParameter = comboBox.DisplayBindingPath
});
}
}
/// <summary>
/// Gets the value indicating if the object is loaded or not
/// </summary>
/// <param name="obj">DependencyObject</param>
/// <returns>the value indicating if the object is loaded or not</returns>
public static bool GetSelectionBoxLoaded(DependencyObject obj)
{
return (bool)obj.GetValue(SelectionBoxLoadedProperty);
}
/// <summary>
/// Sets the value indicating if the object is loaded or not
/// </summary>
/// <param name="obj">DependencyObject</param>
/// <param name="value">the value indicating if the object is loaded or not</param>
public static void SetSelectionBoxLoaded(DependencyObject obj, bool value)
{
obj.SetValue(SelectionBoxLoadedProperty, value);
}
/// <summary>
/// ComboBoxItemLoaded called on ComboBoxItem load
/// </summary>
public static DependencyProperty ComboBoxItemLoadedProperty = DependencyProperty.RegisterAttached("ComboBoxItemLoaded",
typeof(bool), typeof(MultiSelectComboBoxService), new PropertyMetadata(false, (obj, e) =>
{
CheckBox targetElement = obj as CheckBox;
if (targetElement != null)
{
targetElement.Loaded += new RoutedEventHandler(comboBoxItem_Loaded);
targetElement.SetBinding(MultiSelectComboBoxService.DataContextProperty, new Binding());
}
}));
private static void comboBoxItem_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
MultiSelectComboBox comboBox = GetComboBox(element);
if (comboBox != null)
{
element.SetBinding(CheckBox.ContentProperty, new Binding(comboBox.DisplayBindingPath));
//Binding binding = new Binding(comboBox.IsCheckedBindingPath);
//binding.Mode = BindingMode.TwoWay;
//element.SetBinding(CheckBox.IsCheckedProperty, binding);
}
}
/// <summary>
///Gets the value indicating if the item is loaded or not
/// </summary>
/// <param name="obj">DependencyObject</param>
/// <returns>the value indicating if the item is loaded or not</returns>
public static bool GetComboBoxItemLoaded(DependencyObject obj)
{
return (bool)obj.GetValue(ComboBoxItemLoadedProperty);
}
/// <summary>
/// Sets the value indicating if the item is loaded or not
/// </summary>
/// <param name="obj">DependencyObject</param>
/// <param name="value">the value indicating if the item is loaded or not</param>
public static void SetComboBoxItemLoaded(DependencyObject obj, bool value)
{
obj.SetValue(ComboBoxItemLoadedProperty, value);
}
private static MultiSelectComboBox GetComboBox(DependencyObject targetElement)
{
MultiSelectComboBoxItem item = targetElement.GetVisualParent<MultiSelectComboBoxItem>();
if (item != null)
{
return item.ParentComboBox;
}
return null;
}
private static DependencyProperty DataContextProperty = DependencyProperty.RegisterAttached("DataContext",
typeof(object), typeof(MultiSelectComboBoxService), new PropertyMetadata(null, (obj, e) =>
{
CheckBox checkBox = (CheckBox)obj;
MultiSelectComboBox comboBox = GetComboBox(checkBox);
if (comboBox != null)
{
checkBox.IsChecked = comboBox.SelectedItems.Contains(checkBox.DataContext);
}
}));
private static object GetDataContext(DependencyObject obj)
{
return obj.GetValue(DataContextProperty);
}
private static void SetDataContext(DependencyObject obj, object value)
{
obj.SetValue(DataContextProperty, value);
}
}
}
The converter is as follows:
namespace Kepler.SilverlightControls.MultiSelectComboBox
{
#region Méthodes
public class MultiSelectComboxConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string displayMemberPath = parameter as string;
if (String.IsNullOrWhiteSpace(displayMemberPath))
{
return String.Empty;
}
PropertyInfo propertyInfo;
return string.Join(", ", (value as IEnumerable<object>).Select(item =>
{
propertyInfo = DataControlHelper.GetPropertyInfo(item.GetType(), displayMemberPath);
if (propertyInfo == null)
{
return String.Empty;
}
return propertyInfo.GetValue(item, null);
}).ToArray());
}
/// <summary>
/// Not implemented
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
You must not register the dependency property as an attached property, but as a regular dependency property instead. Moreover, you should not use new ObservableCollection<object>() as default property value, as that would use the same collection instance as default value for the property on all instances of your MultiSelectComboBox.
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register( // Register instead of RegisterAttached
"SelectedItems",
typeof(ObservableCollection<object>),
typeof(MultiSelectComboBox),
new PropertyMetadata(SelectedItemsPropertyChanged)); // no default value
I'd also recommend not to use ObservableCollection<object> as the property type, but simply ICollection or IEnumerable instead. This would allow for other implementations of INotifyCollectionChanged in the concrete collection type.
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register(
"SelectedItems",
typeof(ICollection),
typeof(MultiSelectComboBox),
new PropertyMetadata(SelectedItemsPropertyChanged));
public ICollection SelectedItems
{
get { return (ICollection)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
private static void SelectedItemsPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var comboBox = (MultiSelectComboBox)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= SelectedItemsCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += SelectedItemsCollectionChanged;
}
}
private static void SelectedItemsCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
...
}
}
Please also note that the MultiSelectComboBox does not need to implement INotifyPropertyChanged. This would only be necessary for notifying changes of properties that are no dependency properties.
alt text http://[url=http://www.freeimagehosting.net/][img]http://www.freeimagehosting.net/uploads/06e679a07d.jpg[/img][/url]
How to change the button into image button... the button in the beginning has "Pick a date" when clicked a calender pops out and the when a date is selected a label at the bottom reading the date comes in and the text on the button changes to disabled... i want to palce a imagebutton having a image icon of the calender and rest of the function will be the same....
the code as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
[assembly: TagPrefix("DatePicker", "EWS")]
namespace EclipseWebSolutions.DatePicker
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:DatePicker runat=server>")]
[DefaultEvent("SelectionChanged")]
[ValidationProperty("TextValue")]
public class DatePicker : WebControl, INamingContainer
{
#region Properties
public TextBox txtDate = new TextBox();
public Calendar calDate = new Calendar();
public Button btnDate = new Button();
public Panel pnlCalendar = new Panel();
private enum ViewStateConstants
{
ValidationGroup,
RegularExpression,
ErrorMessage,
RegExText,
CalendarPosition,
FormatString,
ExpandLabel,
CollapseLabel,
ApplyDefaultStyle,
CausesValidation,
}
/// <summary>
/// Defines the available display modes of this calendar.
/// </summary>
public enum CalendarDisplay
{
DisplayRight,
DisplayBelow
}
/// <summary>
/// Where to display the popup calendar.
/// </summary>
[Category("Behaviour")]
[Localizable(true)]
public CalendarDisplay CalendarPosition
{
get
{
if (ViewState[ViewStateConstants.CalendarPosition.ToString()] == null)
{
ViewState[ViewStateConstants.CalendarPosition.ToString()] = CalendarDisplay.DisplayRight;
}
return (CalendarDisplay)ViewState[ViewStateConstants.CalendarPosition.ToString()];
}
set
{
ViewState[ViewStateConstants.CalendarPosition.ToString()] = value;
}
}
/// <summary>
/// Text version of the control's value, for use by ASP.NET validators.
/// </summary>
public string TextValue
{
get { return txtDate.Text; }
}
/// <summary>
/// Holds the current date value of this control.
/// </summary>
[Category("Behaviour")]
[Localizable(true)]
[Bindable(true, BindingDirection.TwoWay)]
public DateTime DateValue
{
get
{
try
{
if (txtDate.Text == "") return DateTime.MinValue;
DateTime val = DateTime.Parse(txtDate.Text);
return val;
}
catch (ArgumentNullException)
{
return DateTime.MinValue;
}
catch (FormatException)
{
return DateTime.MinValue;
}
}
set
{
if (value == DateTime.MinValue)
{
txtDate.Text = "";
}
else
{
txtDate.Text = value.ToShortDateString();
}
}
}
[Category("Behavior"), Themeable(false), DefaultValue("")]
public virtual string ValidationGroup
{
get
{
if (ViewState[ViewStateConstants.ValidationGroup.ToString()] == null)
{
return string.Empty;
}
else
{
return (string)ViewState[ViewStateConstants.ValidationGroup.ToString()];
}
}
set
{
ViewState[ViewStateConstants.ValidationGroup.ToString()] = value;
}
}
/// <summary>
/// The label of the exand button. Shown when the calendar is hidden.
/// </summary>
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("PickDate")]
[Localizable(true)]
public string ExpandButtonLabel
{
get
{
String s = (String)ViewState[ViewStateConstants.ExpandLabel.ToString()];
return ((s == null) ? "PickDate" : s);
}
set
{
ViewState[ViewStateConstants.ExpandLabel.ToString()] = value;
}
}
/// <summary>
/// The label of the collapse button. Shown when the calendar is visible.
/// </summary>
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("Disabled")]
[Localizable(true)]
public string CollapseButtonLabel
{
get
{
String s = (String)ViewState[ViewStateConstants.CollapseLabel.ToString()];
return ((s == null) ? "Disabled" : s);
}
set
{
ViewState[ViewStateConstants.CollapseLabel.ToString()] = value;
}
}
/// <summary>
/// Whether to apply the default style. Disable this if you want to apply a custom style, or to use themes and skins
/// to style the control.
/// </summary>
[Category("Appearance")]
[DefaultValue(true)]
[Localizable(true)]
public bool ApplyDefaultStyle
{
get
{
if (ViewState[ViewStateConstants.ApplyDefaultStyle.ToString()] == null)
{
ViewState[ViewStateConstants.ApplyDefaultStyle.ToString()] = true;
}
return (bool)ViewState[ViewStateConstants.ApplyDefaultStyle.ToString()];
}
set
{
ViewState[ViewStateConstants.ApplyDefaultStyle.ToString()] = value;
}
}
/// <summary>
/// Causes Validation
/// </summary>
[Category("Appearance")]
[DefaultValue(false)]
[Localizable(false)]
public bool CausesValidation
{
get
{
if (ViewState[ViewStateConstants.CausesValidation.ToString()] == null)
{
ViewState[ViewStateConstants.CausesValidation.ToString()] = false;
}
return (bool)ViewState[ViewStateConstants.CausesValidation.ToString()];
}
set
{
ViewState[ViewStateConstants.CausesValidation.ToString()] = value;
btnDate.CausesValidation = value;
}
}
#endregion
#region Events
/// <summary>
/// A day was selected from the calendar control.
/// </summary>
public event EventHandler SelectionChanged;
protected virtual void OnSelectionChanged()
{
if (SelectionChanged != null) // only raise the event if someone is listening.
{
SelectionChanged(this, EventArgs.Empty);
}
}
#endregion
#region Event Handlers
/// <summary>
/// The +/- button was clicked.
/// </summary>
protected void btnDate_Click(object sender, System.EventArgs e)
{
if (!calDate.Visible)
{
// expand the calendar
calDate.Visible = true;
txtDate.Enabled = false;
btnDate.Text = CollapseButtonLabel;
if (DateValue != DateTime.MinValue)
{
calDate.SelectedDate = DateValue;
calDate.VisibleDate = DateValue;
}
}
else
{
// collapse the calendar
calDate.Visible = false;
txtDate.Enabled = true;
btnDate.Text = ExpandButtonLabel;
}
}
/// <summary>
/// A date was selected from the calendar.
/// </summary>
protected void calDate_SelectionChanged(object sender, System.EventArgs e)
{
calDate.Visible = false;
txtDate.Visible = true;
btnDate.Text = ExpandButtonLabel;
txtDate.Enabled = true;
txtDate.Text = calDate.SelectedDate.ToShortDateString();
OnSelectionChanged();
}
#endregion
/// <summary>
/// Builds the contents of this control.
/// </summary>
protected override void CreateChildControls()
{
btnDate.Text = ExpandButtonLabel;
btnDate.CausesValidation = CausesValidation;
txtDate.ID = "txtDate";
calDate.Visible = false;
if (ApplyDefaultStyle)
{
calDate.BackColor = System.Drawing.Color.White;
calDate.BorderColor = System.Drawing.Color.FromArgb(10066329);
calDate.CellPadding = 2;
calDate.DayNameFormat = DayNameFormat.Shortest;
calDate.Font.Name = "Verdana";
calDate.Font.Size = FontUnit.Parse("8pt");
calDate.ForeColor = System.Drawing.Color.Black;
calDate.Height = new Unit(150, UnitType.Pixel);
calDate.Width = new Unit(180, UnitType.Pixel);
calDate.DayHeaderStyle.BackColor = System.Drawing.Color.FromArgb(228, 228, 228);
calDate.DayHeaderStyle.Font.Size = FontUnit.Parse("7pt");
calDate.TitleStyle.Font.Bold = true;
calDate.WeekendDayStyle.BackColor = System.Drawing.Color.FromArgb(255, 255, 204);
}
ConnectEventHandlers();
pnlCalendar.Controls.Add(calDate);
pnlCalendar.Style["position"] = "absolute";
pnlCalendar.Style["filter"] = "alpha(opacity=95)";
pnlCalendar.Style["-moz-opacity"] = ".95";
pnlCalendar.Style["opacity"] = ".95";
pnlCalendar.Style["z-index"] = "2";
pnlCalendar.Style["background-color"] = "White";
if (CalendarPosition == CalendarDisplay.DisplayBelow)
{
pnlCalendar.Style["margin-top"] = "27px";
}
else
{
pnlCalendar.Style["display"] = "inline";
}
Controls.Add(txtDate);
Controls.Add(pnlCalendar);
Controls.Add(btnDate);
base.CreateChildControls();
}
/// <summary>
/// Render the contents of this control.
/// </summary>
/// <param name="output">The HtmlTextWriter to use.</param>
protected override void RenderContents(HtmlTextWriter output)
{
switch (CalendarPosition)
{
case CalendarDisplay.DisplayRight:
{
txtDate.RenderControl(output);
btnDate.RenderControl(output);
pnlCalendar.RenderControl(output);
break;
}
case CalendarDisplay.DisplayBelow:
{
pnlCalendar.RenderControl(output);
txtDate.RenderControl(output);
btnDate.RenderControl(output);
break;
}
}
}
/// <summary>
/// Connect event handlers to events.
/// </summary>
private void ConnectEventHandlers()
{
btnDate.Click += new System.EventHandler(btnDate_Click);
calDate.SelectionChanged += new System.EventHandler(calDate_SelectionChanged);
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
[assembly: TagPrefix("DatePicker", "EWS")]
namespace EclipseWebSolutions.DatePicker
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:DatePicker runat=server>")]
[DefaultEvent("SelectionChanged")]
[ValidationProperty("TextValue")]
public class DatePicker : WebControl, INamingContainer
{
#region Properties
public TextBox txtDate = new TextBox();
public Calendar calDate = new Calendar();
public Button btnDate = new Button();
public Panel pnlCalendar = new Panel();
private enum ViewStateConstants
{
ValidationGroup,
RegularExpression,
ErrorMessage,
RegExText,
CalendarPosition,
FormatString,
ExpandLabel,
CollapseLabel,
ApplyDefaultStyle,
CausesValidation,
}
/// <summary>
/// Defines the available display modes of this calendar.
/// </summary>
public enum CalendarDisplay
{
DisplayRight,
DisplayBelow
}
/// <summary>
/// Where to display the popup calendar.
/// </summary>
[Category("Behaviour")]
[Localizable(true)]
public CalendarDisplay CalendarPosition
{
get
{
if (ViewState[ViewStateConstants.CalendarPosition.ToString()] == null)
{
ViewState[ViewStateConstants.CalendarPosition.ToString()] = CalendarDisplay.DisplayRight;
}
return (CalendarDisplay)ViewState[ViewStateConstants.CalendarPosition.ToString()];
}
set
{
ViewState[ViewStateConstants.CalendarPosition.ToString()] = value;
}
}
/// <summary>
/// Text version of the control's value, for use by ASP.NET validators.
/// </summary>
public string TextValue
{
get { return txtDate.Text; }
}
/// <summary>
/// Holds the current date value of this control.
/// </summary>
[Category("Behaviour")]
[Localizable(true)]
[Bindable(true, BindingDirection.TwoWay)]
public DateTime DateValue
{
get
{
try
{
if (txtDate.Text == "") return DateTime.MinValue;
DateTime val = DateTime.Parse(txtDate.Text);
return val;
}
catch (ArgumentNullException)
{
return DateTime.MinValue;
}
catch (FormatException)
{
return DateTime.MinValue;
}
}
set
{
if (value == DateTime.MinValue)
{
txtDate.Text = "";
}
else
{
txtDate.Text = value.ToShortDateString();
}
}
}
[Category("Behavior"), Themeable(false), DefaultValue("")]
public virtual string ValidationGroup
{
get
{
if (ViewState[ViewStateConstants.ValidationGroup.ToString()] == null)
{
return string.Empty;
}
else
{
return (string)ViewState[ViewStateConstants.ValidationGroup.ToString()];
}
}
set
{
ViewState[ViewStateConstants.ValidationGroup.ToString()] = value;
}
}
/// <summary>
/// The label of the exand button. Shown when the calendar is hidden.
/// </summary>
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("PickDate")]
[Localizable(true)]
public string ExpandButtonLabel
{
get
{
String s = (String)ViewState[ViewStateConstants.ExpandLabel.ToString()];
return ((s == null) ? "PickDate" : s);
}
set
{
ViewState[ViewStateConstants.ExpandLabel.ToString()] = value;
}
}
/// <summary>
/// The label of the collapse button. Shown when the calendar is visible.
/// </summary>
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("Disabled")]
[Localizable(true)]
public string CollapseButtonLabel
{
get
{
String s = (String)ViewState[ViewStateConstants.CollapseLabel.ToString()];
return ((s == null) ? "Disabled" : s);
}
set
{
ViewState[ViewStateConstants.CollapseLabel.ToString()] = value;
}
}
/// <summary>
/// Whether to apply the default style. Disable this if you want to apply a custom style, or to use themes and skins
/// to style the control.
/// </summary>
[Category("Appearance")]
[DefaultValue(true)]
[Localizable(true)]
public bool ApplyDefaultStyle
{
get
{
if (ViewState[ViewStateConstants.ApplyDefaultStyle.ToString()] == null)
{
ViewState[ViewStateConstants.ApplyDefaultStyle.ToString()] = true;
}
return (bool)ViewState[ViewStateConstants.ApplyDefaultStyle.ToString()];
}
set
{
ViewState[ViewStateConstants.ApplyDefaultStyle.ToString()] = value;
}
}
/// <summary>
/// Causes Validation
/// </summary>
[Category("Appearance")]
[DefaultValue(false)]
[Localizable(false)]
public bool CausesValidation
{
get
{
if (ViewState[ViewStateConstants.CausesValidation.ToString()] == null)
{
ViewState[ViewStateConstants.CausesValidation.ToString()] = false;
}
return (bool)ViewState[ViewStateConstants.CausesValidation.ToString()];
}
set
{
ViewState[ViewStateConstants.CausesValidation.ToString()] = value;
btnDate.CausesValidation = value;
}
}
#endregion
#region Events
/// <summary>
/// A day was selected from the calendar control.
/// </summary>
public event EventHandler SelectionChanged;
protected virtual void OnSelectionChanged()
{
if (SelectionChanged != null) // only raise the event if someone is listening.
{
SelectionChanged(this, EventArgs.Empty);
}
}
#endregion
#region Event Handlers
/// <summary>
/// The +/- button was clicked.
/// </summary>
protected void btnDate_Click(object sender, System.EventArgs e)
{
if (!calDate.Visible)
{
// expand the calendar
calDate.Visible = true;
txtDate.Enabled = false;
btnDate.Text = CollapseButtonLabel;
if (DateValue != DateTime.MinValue)
{
calDate.SelectedDate = DateValue;
calDate.VisibleDate = DateValue;
}
}
else
{
// collapse the calendar
calDate.Visible = false;
txtDate.Enabled = true;
btnDate.Text = ExpandButtonLabel;
}
}
/// <summary>
/// A date was selected from the calendar.
/// </summary>
protected void calDate_SelectionChanged(object sender, System.EventArgs e)
{
calDate.Visible = false;
txtDate.Visible = true;
btnDate.Text = ExpandButtonLabel;
txtDate.Enabled = true;
txtDate.Text = calDate.SelectedDate.ToShortDateString();
OnSelectionChanged();
}
#endregion
/// <summary>
/// Builds the contents of this control.
/// </summary>
protected override void CreateChildControls()
{
btnDate.Text = ExpandButtonLabel;
btnDate.CausesValidation = CausesValidation;
txtDate.ID = "txtDate";
calDate.Visible = false;
if (ApplyDefaultStyle)
{
calDate.BackColor = System.Drawing.Color.White;
calDate.BorderColor = System.Drawing.Color.FromArgb(10066329);
calDate.CellPadding = 2;
calDate.DayNameFormat = DayNameFormat.Shortest;
calDate.Font.Name = "Verdana";
calDate.Font.Size = FontUnit.Parse("8pt");
calDate.ForeColor = System.Drawing.Color.Black;
calDate.Height = new Unit(150, UnitType.Pixel);
calDate.Width = new Unit(180, UnitType.Pixel);
calDate.DayHeaderStyle.BackColor = System.Drawing.Color.FromArgb(228, 228, 228);
calDate.DayHeaderStyle.Font.Size = FontUnit.Parse("7pt");
calDate.TitleStyle.Font.Bold = true;
calDate.WeekendDayStyle.BackColor = System.Drawing.Color.FromArgb(255, 255, 204);
}
ConnectEventHandlers();
pnlCalendar.Controls.Add(calDate);
pnlCalendar.Style["position"] = "absolute";
pnlCalendar.Style["filter"] = "alpha(opacity=95)";
pnlCalendar.Style["-moz-opacity"] = ".95";
pnlCalendar.Style["opacity"] = ".95";
pnlCalendar.Style["z-index"] = "2";
pnlCalendar.Style["background-color"] = "White";
if (CalendarPosition == CalendarDisplay.DisplayBelow)
{
pnlCalendar.Style["margin-top"] = "27px";
}
else
{
pnlCalendar.Style["display"] = "inline";
}
Controls.Add(txtDate);
Controls.Add(pnlCalendar);
Controls.Add(btnDate);
base.CreateChildControls();
}
/// <summary>
/// Render the contents of this control.
/// </summary>
/// <param name="output">The HtmlTextWriter to use.</param>
protected override void RenderContents(HtmlTextWriter output)
{
switch (CalendarPosition)
{
case CalendarDisplay.DisplayRight:
{
txtDate.RenderControl(output);
btnDate.RenderControl(output);
pnlCalendar.RenderControl(output);
break;
}
case CalendarDisplay.DisplayBelow:
{
pnlCalendar.RenderControl(output);
txtDate.RenderControl(output);
btnDate.RenderControl(output);
break;
}
}
}
/// <summary>
/// Connect event handlers to events.
/// </summary>
private void ConnectEventHandlers()
{
btnDate.Click += new System.EventHandler(btnDate_Click);
calDate.SelectionChanged += new System.EventHandler(calDate_SelectionChanged);
}
}
}
Untitled Page
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void DatePicker1_SelectionChanged(object sender, EventArgs e)
{
Label1.Text = DatePicker1.DateValue.ToShortDateString();
pnlLabel.Update();
}
}
to make the button an image you could just style it to add a background image.
btnDate.style["background-image"] ="url(images/btn.jpg)";
btnDate.text="";
In Html, an image button has a type attribute of 'image' and a src attribute used to determine the image to be displayed. So, to change a standard button to an image button you would:
buttonControl.Attributes["type"] = "image";
buttonControl.Attributes["src"] = "Foo.jpg";
EDIT
If you are trying to change the button type in the click event, then you might be required to do that via javascript client-side. You can do that by registering a start-up script in the Click event like so:
protected void Button_OnClick( object sender, EventArgs e )
{
var button = sender as Button;
if ( button == null )
return;
var jsScript = new StringBuilder();
jsScript.AppendFormat( "<script type=\"text/javascript\">" );
jsScript.AppendFormat( "var button = document.getElementById(\"{0}\"); button.type = \"image\"; button.src=\"Foo.jpg\";"
, button.ClientID);
jsScript.Append( "</script>" );
if ( !Page.ClientScript.IsStartupScriptRegistered( typeof( Page ), "resetImageButton" ) )
Page.ClientScript.RegisterStartupScript( typeof( Page ), "resetImageButton", jsScript.ToString(), false );
}
The catch is that this will only work right after the postback triggered by clicking the button. It will not survive multiple postbacks. For that, you would need to store a flag in a hidden text field or ViewState that indicates which type the button should be and then, based on that, determine whether you need to register this startup script or not.