I made my own slider as user control with some custom properties and one custom event. Everything works fine, but recently I start using Caliburn Micro, and I don't know how to capture my custom event.
Previously I used:
<my:RadialSlider x:Name="slider" WedgeAngle="270" ..... AngleChanged="slider_AngleChanged" />
and
public void slider_AngleChanged(object sender, ValueChangedEventArgs e)
{
.... something ....
}
Now, in Caliburn project I tried:
<my:RadialSlider x:Name="slider" WedgeAngle="270" ..... cal:Message.Attach="[Event AngleChanged] = [Action slider_AngleChanged($eventArgs)]" />
and in my ViewModel:
public void slider_AngleChanged(object sender, ValueChangedEventArgs e)
{
.... something ....
}
But, this doesn't work...
So, how to capture this event?
Slider UC code-behind:
public delegate void AngleChangedEventHandler(object sender, ValueChangedEventArgs e);
public sealed partial class RadialSlider : UserControl
{
public event AngleChangedEventHandler AngleChanged;
private void OnAngleChanged(ValueChangedEventArgs e)
{
if (AngleChanged != null)
AngleChanged(this, e);
}
public static readonly DependencyProperty WedgeAngleProperty = DependencyProperty.Register("WedgeAngle", typeof(double), typeof(RadialSlider), new PropertyMetadata((double)270, new PropertyChangedCallback(OnPropertyWedgeAngleChanged)));
public double WedgeAngle
{
get { return (double)this.GetValue(WedgeAngleProperty); }
set { this.SetValue(WedgeAngleProperty, value); }
}
private static void OnPropertyWedgeAngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as RadialSlider).UpdateControls();
if (e.NewValue != e.OldValue)
{
(sender as RadialSlider).OnAngleChanged(new ValueChangedEventArgs((double)e.OldValue, (double)e.NewValue));
}
}
}
You need to use a routed event. This has to do with how events bubble up the visual tree and how Caliburn.Micro attaches itself to them. Standard events should be avoided on controls or UI widgets in any tech using Xaml as the loose out on some pretty funky features (bubble up / down).
Related
I have a user control which has a button and a dependency property for the action the button is to execute. The page which contains the control sets the action in XAML.
MyUserControl.cs
A Button, and dependency property ButtonAction, of type Action. When the button is clicked it executes the ButtonAction.
MainPage.xaml.cs
Action Action1
Action Action2
MainPage.xaml
Present an instance of MyUserControl, with ButtonAction=Action1
The problem: The ButtonAction property is not assigned from the XAML
MyUserControl.cs
public sealed partial class MyUserControl : UserControl
{
public Action ButtonAction {
get { return (Action)GetValue(ButtonActionProperty); }
set { SetValue(ButtonActionProperty, value); }
}
public static readonly DependencyProperty ButtonActionProperty =
DependencyProperty.Register("ButtonAction", typeof(Action), typeof(MyUserControl), new PropertyMetadata(null,ButtonAction_PropertyChanged));
private static void ButtonAction_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
Debug.WriteLine("ButtonAction_PropertyChanged");
// Is not called!
}
public MyUserControl() {
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
if (ButtonAction != null) {
// Never reaches here!
ButtonAction();
}
}
}
MyUserControl.xaml
<Grid>
<Button Click="Button_Click">Do The Attached Action!</Button>
</Grid>
MainPage.xaml.cs
Action Action1 = (
() => { Debug.WriteLine("Action1 called"); });
Action Action2 = (() => { Debug.WriteLine("Action2 called"); });
MainPage.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:MyUserControl x:Name="myUserControl" ButtonAction="{Binding Action1}"/>
</Grid>
It does work if in the code-behind for MainPage (MainPage.xaml.cs) I assign the action in the Loaded event.
private void Page_Loaded(object sender, RoutedEventArgs e) {
this.myUserControl.ButtonAction = Action1;
}
In this case the PropertyChanged callback in the user control is also called. (This handler is provided only for diagnostic purposes. I can't see how it can be used to support the property in practice).
The issue is in your data binding. The Action1 in ButtonAction="{Binding Action1}" should be a public property while you defined it as a private variable.
Also, you cannot just declare a normal property directly in the code behind like that. You will need either a dependency property, or more commonly, a public property inside a viewmodel which implements INotifyPropertyChanged.
If we go with the second approach, we will need to create a viewmodel class like the following with an Action1 property. Note the OnPropertyChanged stuff is just the standard way of implementing INotifyPropertyChanged.
public class ViewModel : INotifyPropertyChanged
{
private Action _action1;
public Action Action1
{
get { return _action1; }
set
{
_action1 = value;
OnPropertyChanged("Action1");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
And then you just need to assign this to the DataContext of your main page.
public MainPage()
{
this.InitializeComponent();
var vm = new ViewModel();
vm.Action1 = (() =>
{
Debug.WriteLine("Action1 called");
});
this.DataContext = vm;
}
With these two changes, your ButtonAction callback should be firing now. :)
I am trying to save a list of backstack pages that are Tombstoned, so that when I navigate back to them, I can compare if they are present in this list. If yes, I'll restore its state.
Currently my code looks like this.
public partial class App : Application
{
public static List<PhoneApplicationPage> TombstonedPages = new List<PhoneApplicationPage>();
private void Application_Activated(object sender, ActivatedEventArgs e)
{
if(!e.IsApplicationInstancePreserved)
{
foreach (JournalEntry j in (Application.Current.RootVisual as PhoneApplicationFrame).BackStack)
{
TombstonedPages.Add(//What should i add here);
}
}
}
}
code in some PhoneApplicationPage
protected override void OnNavigatedTo(NavigationEventArgs e)
{
//checking tombstone
if(e.NavigationMode== NavigationMode.Back && App.TombstonedPages.Contains(this) )
{
//restore state and delete entry from App.TombstonedPages
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
if(e.NavigationMode != NavigationMode.Back)
{
//save state
}
}
But I am unable to get a reference of pages from backstack. How should I do this? Is there a different way to do this?
I want to update a textblock whenever there is a change in battery percent. I found the event RemainingChargePercentChanged in the Windows.Phone.Devices.Power.Battery namespace. But whenever the eventhandler is called and i try to update the textblock, i struck with error.
the error is:
An exception of type System.UnauthorizedAccessException occurred in System.Windows.ni.dll but was not handled in user code.
Do I want to select any capabilities in AppManifest file??.. If so, what?
Any help will be appreciated.
Following is my code,
public partial class MainPage : PhoneApplicationPage
{
private readonly Battery _battery;
// Constructor
public MainPage()
{
InitializeComponent();
_battery = Battery.GetDefault();
_battery.RemainingChargePercentChanged += OnRemainingChargePercentChanged;
UpdateUI();
}
private void OnRemainingChargePercentChanged(object sender, object e)
{
UpdateUI();
}
private void UpdateUI()
{
sampletext.Text = string.Format("{0} %", _battery.RemainingChargePercent);
}
}
The problem is that the event handler is called on another thread, if you read the exception message it will say Invalid cross-thread access.
The solution is to change the Text property on the UI thread using the Dispatcher, like this:
Dispatcher.BeginInvoke(() => {
sampletext.Text = string.Format("{0} %", _battery.RemainingChargePercent);
});
Edit: or your whole UpdateUI function call:
private void OnRemainingChargePercentChanged(object sender, object e)
{
Dispatcher.BeginInvoke(() => {
UpdateUI();
});
}
I have a usercontrol and tap event which is in usercontrol itself..And am having holding event for that usercontrol in phoneapplication page which is parent page.I want to raise tap event from hold event how do i achieve this?..
ParentPage is having:
<DataTemplate x:Key="template" >
<chatbubble:ChatBubbleControl x:Name="ChatBubble" Hold="ChatBubbleControl_Hold_1" />
</DataTemplate>
UserControl is having..
<UserControl.Resources>
....
<Grid x:Name="LayoutRoot" Background="Transparent" Width="455" Tap="chatBubble_Tap" >
.....
</Grid>
I want to raise chatBubble_Tap event from ChatBubbleControl_Hold_1
You can try to raise your event like this:
// make your eventhandler in Parent Page public and static so it will be available thru all the App
public static void chatBubble_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
// your code
}
// then in your UserControl you should be able to call it like this:
private void ChatBubbleControl_Hold_1(object sender, System.Windows.Input.GestureEventArgs e)
{
YourPage.chatBubble_Tap(sender, e);
}
This case depends on what you have in your Tap event (not everything can be in static method).
You can also pass the handler of your Page to your UserControl and then invoke your Tap event from handler (in this case the Tap event can be public (non-static). Simple case can look like this:
// your Control in MainPage (or other Page)
<local:myControl x:Name="yourControl" VerticalAlignment="Center" Grid.Row="1"/>
// initializing control and event to be invoked:
public MainPage()
{
InitializeComponent();
yourControl.pageHandler = this;
}
public void second_Click(object sender, RoutedEventArgs e)
{
// something here
}
// and the control code:
public partial class myControl : UserControl
{
public Page pageHandler;
public myControl()
{
InitializeComponent();
myButton.Hold +=myButton_Hold;
}
private void myButton_Hold(object sender, System.Windows.Input.GestureEventArgs e)
{
if (pageHandler is MainPage) (pageHandler as MainPage).second_Click(sender, e);
}
}
The third option would be to pass an Action to your Control (in this case event can be private):
// code in MainPage (or your Page)
public MainPage()
{
InitializeComponent();
yourControl.myAction = second_Click; // setting an action of Control
}
private void second_Click(object sender, RoutedEventArgs e)
{
// something here
}
// and the Control class
public partial class myControl : UserControl
{
public Action<object, System.Windows.Input.GestureEventArgs> myAction;
public myControl()
{
InitializeComponent();
myButton.Hold +=myButton_Hold;
}
private void myButton_Hold(object sender, System.Windows.Input.GestureEventArgs e)
{
if (myAction != null) myAction.Invoke(sender, e);
}
}
As described i input a datepicker in my xaml file
when i run the page ,datepicker just show like this:
then I have to tap the datepicker to enter the select page like this :
Now
I need to directly open the fullscreen datepicker select page when I click a button
the address give a way that I can just Navigate to the select page,
but I don't know how ?
I'm the poster.
i find a solution myself
Override DatePicker class with our custom DatePickerCustom class. Create new class "DatePickerCustom.cs"
public class DatePickerCustom : DatePicker
{
public void ClickTemplateButton()
{
Button btn = (GetTemplateChild("DateTimeButton") as Button);
ButtonAutomationPeer peer = new ButtonAutomationPeer(btn);
IInvokeProvider provider = (peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider);
provider.Invoke();
}
}
then in the mainpage.xaml.cs
private DatePickerCustom datePicker;
// Constructor
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// create datePicker programmatically
if (this.datePicker == null)
{
this.datePicker = new DatePickerCustom();
this.datePicker.IsTabStop = false;
this.datePicker.MaxHeight = 0;
this.datePicker.ValueChanged += new EventHandler<DateTimeValueChangedEventArgs>(datePicker_ValueChanged);
LayoutRoot.Children.Add(this.datePicker);
}
}
void datePicker_ValueChanged(object sender, DateTimeValueChangedEventArgs e)
{
// now we may use got value from datePicker
TextBlock1.Text = this.datePicker.ValueString;
}
so that when do an action like tap or click, the fullscreen datepicker page will be shown
private void button1_Click(object sender, RoutedEventArgs e)
{
this.datePicker.ClickTemplateButton();
}
ps: timepicker can also do the same thing
ps2:here is the details
#Mario Galván
hope it help u