I am developing a Windows phone app, and I have this logic:
I have a chat view, with ListView which contain video or text messages. My video messages are custom controls which implements my IPlayableMessage interface:
public class VideoMessage : Grid , IPlayableMessage
{
public VideoMessage()
{
this.Loaded += Loaded;
}
private void Loaded(object sender, RoutedEventArgs e)
{
MessagePlayer.Sub(this);
}
public void Play()
{
//Some logic for video play
}
public Player MessagePlayer
{
get { return (Player)GetValue(MessagePlayerProperty); }
set { SetValue(MessagePlayerProperty, value); }
}
public static readonly DependencyProperty MessagePlayerProperty =
DependencyProperty.Register("MessagePlayer", typeof(Player), typeof(VideoMessage), null);
}
This is my interface:
public interface IPlayableMessage
{
void Play(Uri uri = null);
}
In the constructor I am calling Sub method with this parameter, which enabls my in the future to play my video message.
This works great, however if I add a new message to my ListView (and I see it on the view) the controls constructor isn't called and by that my object is not passing it self to the Player.
Why the constructor and Loaded events are not called when new element added to the ListView?
The constructor called thought, as I scroll up and down the ListView and it performs its paging....
After a while I've moved my registration to:
protected override Size ArrangeOverride(Size finalSize)
It reduced the numbers of unregistered grids but it's called several times per object and it's also does not cover a 100 percent of the items.
Any help will be appreciated. :)
Did you try to add callback to the MessagePlayer property and handle operation there?
public static readonly DependencyProperty MessagePlayerProperty = DependencyProperty.Register(
"MessagePlayer", typeof (Player), typeof (VideoMessage), new PropertyMetadata(default(Player), PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as VideoMessage;
(e.NewValue as Player).Sub(control);
}
Related
I am trying to bind without success a property called PointerValue to a NeedlePointer.Value progmatically but seem to have got lost somewhere.
The xamarin app basically has a gauge and a start button when the start button is pressed I start the timer. Upon timer elapsed the needle value should increase by on. Easy in XAML but cant figure out how to convert this to code <gauge:NeedlePointer Value="{Binding PointerValue}"
public class StopWatchPage : BaseContentPage
{
private Timer timer;
private double PointerValue
{
get => (double)GetValue(PointerValueProperty);
set => SetValue(PointerValueProperty, value);
}
private static readonly BindableProperty PointerValueProperty =
BindableProperty.Create("PointerValue",
typeof(double), typeof(StopWatchPage), 0d);
public StopWatchPage()
{
this.BindingContext = this;
var needlePointer = new NeedlePointer
{
Value = PointerValue
};
needlePointer.SetBinding(
PointerValueProperty, nameof(PointerValue));
var scale = new Scale{...};
scale.Pointers.Add(needlePointer);
scales.Add(scale);
circularGauge.Scales = scales;
... add gauge to Content etc...
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
this.PointerValue += 1;
}
}
this should work, although the actual name of ValueProperty might be different depending on how NeedlePointer is implemented. The first argument is the name of the control property that you are binding to (the target), and the second is the name of the value property acts as the source.
needlePointer.SetBinding(NeedlePointer.ValueProperty, "PointerValue");
however, if you want the UI to update dynamically, you will also need to have your BindingContext implement INotifyPropertyChanged
There is no need to create a BindableProperty
Solution thanks to #Jason pointing me to the fact that I needed a model that implements INotifyPropertyChanged so code changed to
public class StopWatchViewModel : INotifyPropertyChanged
{
public double PointerValue { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class StopWatchPage : BaseContentPage
{
private Timer timer;
private readonly StopWatchViewModel model = new StopWatchViewModel();
public StopWatchPage()
{
BindingContext = model;
...
needlePointer.SetBinding(NeedlePointer.ValueProperty,
nameof(model.PointerValue));
...
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
model.PointerValue += 1;
}
}
Simple question. Having:
<ScrollBar ... />
How can I detect when Maximum is changed? E.g. for Value there is an event.
Typically there would be a binding of some kind. I was thinking maybe it is possible to get this binding, create dependency property and bind to it instead, then I can register a callback when this new dependency property is changed... but that sounds complicated nor I am sure it is acceptable solution to all cases (e.g. what if another binding is set, how can I detect this kind of change). Polling?
You can create a custom class such as:
public class MScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
{
// do stuff
base.OnMaximumChanged(oldMaximum, newMaximum);
}
}
Or
public class MScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == System.Windows.Controls.Primitives.ScrollBar.MaximumProperty)
{
// do stuff
}
base.OnPropertyChanged(e);
}
}
It is important to understand what any property can be a source for multiple bindings. We can create a new target (new dependency property) which is then perfectly able to report about any change done to a property:
Create a new dependency property with callback.
Bind it to any other property to monitor for changes.
public partial class MainWindow : Window
{
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(MainWindow), new PropertyMetadata(0, (d, e) =>
{
// value has changed
}));
public MainWindow()
{
InitializeComponent();
var scrollBar = ... // instance of scrollbar
BindingOperations.SetBinding(this, MaximumProperty,
new Binding(nameof(RangeBase.Maximum)) { Source = scrollBar });
}
}
I have a strange problem with the WebView component in WinRT. I use it to display either a Website via Soure property binding or local content with NavigateToString.
My problem is the following:
If I change the Source property, it naviagtes to the Website. Then, I call NavigateToString and it displays the content correctly. After that I change the Source property again, but navigation doesn't start and the local content remains present.
Also if I first call NavigateToString and then change Source property, no navigation starts.
I prepared a small sample project to illustrate my problem: http://1drv.ms/1HI8p3I
EDIT:
I updated the sample project above. I tried to call Naviagte via an Attached Property instead of changing the Source property but I got the same issue. Once I call NavigateToString, all other updates of bindings don't work.
This is my class with the Attached properties for WebView:
public class WebViewEx
{
#region ContentString
public static readonly DependencyProperty ContentStringProperty =
DependencyProperty.RegisterAttached("ContentString", typeof(string), typeof(WebViewEx), new PropertyMetadata("", new PropertyChangedCallback(OnContentStringPropertyChanged)));
public static void SetContentString(DependencyObject obj, string contentString)
{
obj.SetValue(ContentStringProperty, contentString);
}
public static string GetContentString(DependencyObject obj)
{
return (string)obj.GetValue(ContentStringProperty);
}
private static void OnContentStringPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WebView webView = d as WebView;
if (webView != null)
{
webView.NavigateToString((string)e.NewValue);
}
}
#endregion
#region UriString
public static readonly DependencyProperty UriStringProperty =
DependencyProperty.RegisterAttached("UriString", typeof(string), typeof(WebViewEx), new PropertyMetadata("", new PropertyChangedCallback(OnUriStringPropertyChanged)));
public static void SetUriString(DependencyObject obj, string uriString)
{
obj.SetValue(UriStringProperty, uriString);
}
public static string GetUriString(DependencyObject obj)
{
return (string)obj.GetValue(UriStringProperty);
}
private static void OnUriStringPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WebView webView = d as WebView;
if (webView != null)
{
webView.Navigate(new Uri((string)e.NewValue));
}
}
#endregion
}
I am a new to the C# and WPF. Right now, I want to override onTouchUp event in my user control. Then I can add user control in the references for reuse. The problem is each time I want to test this event on the application, the event only fires at area of user control, not whole screen. Does anyone have a solution for this?
Basically, you need to attach to PreviewTouchUp event on a element, that covers the interactive area. It may be application wndows as well. Handling mouse or touch events outside window is not recommended.
Here's an example, how can you attach any behaviour to any element directly in xaml:
<Window my:MyBehaviour.DoSomethingWhenTouched="true" x:Class="MyProject.MainWindow">
public static class MyBehaviour
{
public static readonly DependencyProperty DoSomethingWhenTouchedProperty = DependencyProperty.RegisterAttached("DoSomethingWhenTouched", typeof(bool), typeof(MyBehaviour),
new FrameworkPropertyMetadata( DoSomethingWhenTouched_PropertyChanged));
private static void DoSomethingWhenTouched_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uiElement = (UIElement)d;
//unsibscribe firt to avoid multiple subscription
uiElement.PreviewTouchUp -=uiElement_PreviewTouchUp;
if ((bool)e.NewValue){
uiElement.PreviewTouchUp +=uiElement_PreviewTouchUp;
}
}
static void uiElement_PreviewTouchUp(object sender, System.Windows.Input.TouchEventArgs e)
{
//you logic goes here
}
//methods required by wpf conventions
public static bool GetDoSomethingWhenTouched(UIElement obj)
{
return (bool)obj.GetValue(DoSomethingWhenTouchedProperty);
}
public static void SetDoSomethingWhenTouched(UIElement obj, bool value)
{
obj.SetValue(DoSomethingWhenTouchedProperty, value);
}
}
I have a complex Plot RenderingControl that I have placed into a View. What would be the ideal way to handle zooming with respect to the MVVM pattern? I want the user to be able to zoom by clicking and dragging on the plot.
One approach I see would be to take the MouseMove, MouseUp, MouseDown events of the Plot control and wire them up to commands in the PlotViewModel. Now in response to the commands the ViewModel could update it's ZoomLevel property, which could be bound to the view, and cause the View to zoom in. While the user is clicking and dragging I would also like to display a rectangle indicating the region that will be zoomed. Would it make sense to keep an AnnotationViewModel in PlotViewModel for the zoom preview?
Another approach would be to handle it all in the View and not involve the ViewModel at all.
The main difference I see is that capturing the behavior in the ViewModel will make that behavior much more re-useable than in the View. Though I have a feeling that the underlying Plot control and the resulting View are complex enough that there isn't going to be much of chance for re-use anyway. What do you think?
I think there are several ways to solve your problem. HighCore right, when he says that Zoom applies to View, so it is advisable to leave it on the side View. But there are alternatives, we consider them below. Unfortunately, I did not deal with Plot RenderingControl, so I will describe a solution based on an abstract, independent of the control.
AttachedBehavior
In this case, I would have tried to identify possible all the work with the control via an attached behavior, it is ideally suited for the MVVM pattern, and it can be used in the Blend.
Example of work
In your View, control is defined and an attached behavior, like so:
<RenderingControl Name="MyPlotControl"
AttachedBehaviors:ZoomBehavior.IsStart="True" ... />
And in code-behind:
public static class ZoomBehavior
{
public static readonly DependencyProperty IsStartProperty;
public static void SetIsStart(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsStartProperty, value);
}
public static bool GetIsStart(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsStartProperty);
}
static ZoomBehavior()
{
IsStartMoveProperty = DependencyProperty.RegisterAttached("IsStart",
typeof(bool),
typeof(ZoomBehavior),
new UIPropertyMetadata(false, IsStart));
}
private static void IsStart(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UIElement uiElement = sender as UIElement;
if (uiElement != null)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
uiElement.MouseDown += new MouseButtonEventHandler(ObjectMouseDown);
uiElement.MouseMove += new MouseEventHandler(ObjectMouseMove);
uiElement.MouseUp += new MouseButtonEventHandler(ObjectMouseUp);
}
}
}
// Below is event handlers
}
Once you're set to true for property IsStart, PropertyChanged handler is triggered and it set the handlers for events that contain the basic logic.
For the transmission of additional data in you behavior register additional dependency properties, for example:
<RenderingControl Name="MyPlotControl"
AttachedBehaviors:ZoomBehavior.IsStart="True"
AttachedBehaviors:ZoomBehavior.ZoomValue="50" />
In code-behind:
// ... Here registered property
public static void SetZoomValue(DependencyObject DepObject, int value)
{
DepObject.SetValue(ZoomValueProperty, value);
}
public static int GetZoomValue(DependencyObject DepObject)
{
return (int)DepObject.GetValue(ZoomValueProperty);
}
// ... Somewhere in handler
int value = GetZoomValue(plotControl);
To retrieve data on the behavior, I use a singleton pattern. This pattern represents global static access point to the object and must guarantee the existence of a single instance of the class.
Example of using this pattern (taken from the behavior, who worked with the time display in the View):
public class TimeBehavior : INotifyPropertyChanged
{
// Global instance
private static TimeBehavior _instance = new TimeBehavior();
public static TimeBehavior Instance
{
get
{
return _instance;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private string _currentTime = DateTime.Now.ToString("HH:mm");
public string CurrentTime
{
get
{
return _currentTime;
}
set
{
if (_currentTime != value)
{
_currentTime = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentTime"));
}
}
}
}
private string _currentDayString = ReturnDayString();
public string CurrentDayString
{
get
{
return _currentDayString;
}
set
{
if (_currentDayString != value)
{
_currentDayString = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentDayString"));
}
}
}
}
private string _currentMonthAndDayNumber = ReturnMonthAndDayNumber();
public string CurrentMonthAndDayNumber
{
get
{
return _currentMonthAndDayNumber;
}
set
{
if (_currentMonthAndDayNumber != value)
{
_currentMonthAndDayNumber = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentMonthAndDayNumber"));
}
}
}
}
public static readonly DependencyProperty IsTimerStartProperty;
public static void SetIsTimerStart(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsTimerStartProperty, value);
}
public static bool GetIsTimerStart(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsTimerStartProperty);
}
static void OnIsTimerStartPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1000);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
}
static TimeBehavior()
{
IsTimerStartProperty = DependencyProperty.RegisterAttached("IsTimerStart",
typeof(bool),
typeof(TimeBehavior),
new PropertyMetadata(new PropertyChangedCallback(OnIsTimerStartPropertyChanged)));
}
private static void timer_Tick(object sender, EventArgs e)
{
_instance.CurrentTime = DateTime.Now.ToString("HH:mm");
_instance.CurrentDayString = ReturnDayString();
_instance.CurrentMonthAndDayNumber = ReturnMonthAndDayNumber();
}
}
Access to data in the View:
<TextBlock Name="WidgetTimeTextBlock"
Text="{Binding Path=CurrentTime,
Source={x:Static Member=AttachedBehaviors:TimeBehavior.Instance}}" />
Alternatives
Work in View via Interface
The point of this way is that we call a method in View via ViewModel, which does all the work, and he does not know about the View. This is accomplished by the operation of the interface and the well described here:
Talk to View
Using ServiceLocator
ServiceLocator allows you to work in the ViewModel, without violating the principles of MVVM. You have a RegisterService method where you register the instance of the service you want to provide and a GetService method which you would use to get the service you want.
More information can be found here:
Service Locator in MVVM