I'm trying to pass an object created in MainWindow to my UserControl that will read and modify it but it doesn't don't know why. Here is the code I'm using:
MainWindow class:
public partial class MainWindow : Window
{
public SupremeLibrary.Player player = new SupremeLibrary.Player();
public MainWindow()
{
InitializeComponent();
MusicSeekBar = new Components.SeekBar(player);
}
}
And SeekBar user control:
public partial class SeekBar : UserControl
{
DispatcherTimer Updater = new DispatcherTimer();
SupremeLibrary.Player player;
/// <summary>
/// Initialize new Seekbar
/// </summary>
public SeekBar()
{
InitializeComponent();
InitializeUpdater();
}
public SeekBar(SupremeLibrary.Player _player)
{
player = _player;
InitializeComponent();
InitializeUpdater();
}
private void InitializeUpdater()
{
Updater.Interval = TimeSpan.FromMilliseconds(100);
Updater.Tick += UpdateSeekBar;
Updater.Start();
}
private void UpdateSeekBar(object sender, EventArgs e)
{
if (player != null)
{
if (player.PlaybackState == SupremeLibrary.PlaybackStates.Playing)
{
if (player.Position.TotalMilliseconds != CustomProgressBar.Value) CustomProgressBar.Value = player.Position.TotalMilliseconds;
if (player.MaxPosition.TotalMilliseconds != CustomProgressBar.Maximum) CustomProgressBar.Maximum = player.MaxPosition.TotalMilliseconds;
}
}
}
private void PB_SeekBar_ChangeValue(object obj, MouseEventArgs e)
{
if (player != null)
{
if (player.PlaybackState == SupremeLibrary.PlaybackStates.Playing)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
player.Position = TimeSpan.FromMilliseconds(e.GetPosition(obj as ProgressBar).X / ((obj as ProgressBar).ActualWidth / 100) * ((obj as ProgressBar).Maximum / 100));
}
}
}
}
In add, it works if I use
public SupremeLibrary.Player player = new SupremeLibrary.Player();
as static and call it in UserControl as MainWindow.player but it's ugly and I don't want to use it.
I have tried to pass player from MainWindow as reference but it doesn't seem to work either.
Example using MediaElement
user control SeekBar
XAML
<UserControl x:Class="CSharpWPF.SeekBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}" >
<Slider Maximum="{Binding TotalMilliseconds}"
Value="{Binding CurrentPosition}"/>
</UserControl>
i have defined a Slider in the control with binding to the maximum and value property to the TotalMilliseconds and CurrentPosition of the control, the properties will be bound to the control itself as I have set the DataContext of the control to self
.cs
public partial class SeekBar : UserControl
{
DispatcherTimer Updater = new DispatcherTimer();
/// <summary>
/// Initialize new Seekbar
/// </summary>
public SeekBar()
{
InitializeComponent();
InitializeUpdater();
}
private void InitializeUpdater()
{
Updater.Interval = TimeSpan.FromMilliseconds(100);
Updater.Tick += UpdateSeekBar;
}
public MediaElement Player
{
get { return (MediaElement)GetValue(PlayerProperty); }
set { SetValue(PlayerProperty, value); }
}
// Using a DependencyProperty as the backing store for Player. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlayerProperty =
DependencyProperty.Register("Player", typeof(MediaElement), typeof(SeekBar), new PropertyMetadata(null, OnPlayerChanged));
private static void OnPlayerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SeekBar seekB = d as SeekBar;
if (e.OldValue != null)
{
SeekBar oldSeekB = (e.OldValue as SeekBar);
oldSeekB.Player.MediaOpened -= seekB.Player_MediaOpened;
oldSeekB.Player.MediaEnded -= seekB.Player_MediaEnded;
}
if (seekB.Player != null)
{
seekB.Player.MediaOpened += seekB.Player_MediaOpened;
seekB.Player.MediaEnded += seekB.Player_MediaEnded;
}
}
void Player_MediaEnded(object sender, RoutedEventArgs e)
{
Updater.Stop();
}
private void Player_MediaOpened(object sender, RoutedEventArgs e)
{
if (Player.NaturalDuration.HasTimeSpan)
{
TotalMilliseconds = Player.NaturalDuration.TimeSpan.TotalMilliseconds;
Updater.Start();
}
else
{
CurrentPosition = 0.0;
TotalMilliseconds = 1.0;
}
}
public double CurrentPosition
{
get { return (double)GetValue(CurrentPositionProperty); }
set { SetValue(CurrentPositionProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentPosition. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentPositionProperty =
DependencyProperty.Register("CurrentPosition", typeof(double), typeof(SeekBar), new PropertyMetadata(1.0, OnCurrentPositionChange));
private static void OnCurrentPositionChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SeekBar seekB = d as SeekBar;
if (seekB.Player != null)
{
seekB.Player.Position = TimeSpan.FromMilliseconds(seekB.CurrentPosition);
}
}
public double TotalMilliseconds
{
get { return (double)GetValue(TotalMillisecondsProperty); }
set { SetValue(TotalMillisecondsProperty, value); }
}
// Using a DependencyProperty as the backing store for TotalMilliseconds. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TotalMillisecondsProperty =
DependencyProperty.Register("TotalMilliseconds", typeof(double), typeof(SeekBar), new PropertyMetadata(0.0));
private void UpdateSeekBar(object sender, EventArgs e)
{
if (Player != null && TotalMilliseconds > 1)
{
CurrentPosition = Player.Position.TotalMilliseconds;
}
}
}
what I have done
defined property Player of Media Element to be bound at UI
attached MediaOpened and MediaEnded for the purpose of starting and stopping the timer adn updating the duration.
defined properties for current position and total duration for my slider control in the control UI
and on change of CurrentPosition, I'll update back the player's position.
usage in main window
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<MediaElement x:Name="media"
Source="Wildlife.wmv" />
<l:SeekBar Grid.Row="1"
Player="{Binding ElementName=media}" />
</Grid>
I'll just bind the media element to the Player property of my SeekBar control
by doing in this way I've not done any hard coding in code behind, also by means of interface you can achieve a greater decoupling between your seekbar and the player
this is just a simple example for your case, you may use your custom player and progress control in the above example to achieve your results.
Related
I'm trying to set width of UserControl at initialization time in ViewModel, but it receives only zero. When I resize the window, it receives correct width.
MainWindow.xaml extract:
<local:MapControl x:Name="MapControl"
DataContext="{Binding MapViewModel}"
ActualControlWidth="{Binding ActualControlWidth, Mode=OneWayToSource}" />
MapControl.xaml.cs:
public partial class MapControl : UserControl
{
public MapControl()
{
InitializeComponent();
SizeChanged += OnControlSizeChanged;
}
public static readonly DependencyProperty ActualControlWidthProperty = DependencyProperty.Register(
"ActualControlWidth",
typeof(double),
typeof(MapControl),
new FrameworkPropertyMetadata(PropertyChangedCallback)); //here it does not have any influence if i put default(double) or not
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var newVar = e.NewValue;
}
public double ActualControlWidth
{
get { return (double)GetValue(ActualControlWidthProperty); }
set { SetValue(ActualControlWidthProperty, value); } //here comes the value as 785
}
private void OnControlSizeChanged(object sender, SizeChangedEventArgs e)
{
ActualControlWidth = ActualWidth;
}
}
MapViewModel.cs:
public class MapViewModel : ViewModelBase
{
private double _actualControlWidth;
private Map _map;
public MapViewModel()
{
...
}
public Map Map
{
get => _map;
set => Set(() => Map, ref _map, value);
}
public double ActualControlWidth
{
get => _actualControlWidth;
set => _actualControlWidth = value; //this is where the value comes as 0
}
}
Thanks for your help!
The full reproduction example is at my Github https://github.com/czechdude/dependencypropertyissue
You need to update this line:
new FrameworkPropertyMetadata(PropertyChangedCallback)); //here it does not have any influence if i put default(double) or not
To
new FrameworkPropertyMetadata(defaultValue: -1.0D,
new PropertyChangedCallback(MethodToCall));
...
private static void MethodToCall(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// call second method to make instance of MapControl and call a custom method that sets your ActualControlWidth
}
So I'm working on a C#/WPF Application and I'm relatively new to the language/environment. A couple weeks ago, I asked this question: How can I mimic this behavior in WPF?
I wanted to create a sort of pseudo modal popup that would appear/disappear with a click of a button. Adapting the answer given to me, I created the following two classes:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace RelayC
{
public partial class PopupBase : UserControl
{
public PopupBase()
{
this.Opacity = 0.0;
this.Visibility = Visibility.Hidden;
}
private void OpenPopup()
{
this.Opacity = 1.0;
this.Visibility = Visibility.Visible;
}
private void ClosePopup()
{
this.Opacity = 0.0;
this.Visibility = Visibility.Hidden;
}
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register(nameof(IsOpen),
typeof(bool),
typeof(PopupBase),
new PropertyMetadata(false,
new PropertyChangedCallback((s, e) =>
{
if (s is PopupBase popupBase && e.NewValue is bool boolean)
{
if (boolean)
{
popupBase.OpenPopup();
}
else
{
popupBase.ClosePopup();
}
}
})));
}
public class PopupAttach
{
public static PopupBase GetPopup(ButtonBase button)
=> (PopupBase)button.GetValue(PopupProperty);
public static void SetPopup(ButtonBase button, PopupBase value)
=> button.SetValue(PopupProperty, value);
public static readonly DependencyProperty PopupProperty =
DependencyProperty.RegisterAttached("Popup",
typeof(PopupBase),
typeof(PopupAttach),
new PropertyMetadata(null,
new PropertyChangedCallback((s, e) =>
{
if (s is ButtonBase button && e.NewValue is PopupBase newPopup)
{
if (Application.Current.MainWindow.Content is Grid grid)
{
if (e.OldValue is PopupBase oldPopup)
{
grid.Children.Remove(oldPopup);
}
grid.Children.Add(newPopup);
button.Click -= buttonClick;
button.Click += buttonClick;
}
else
{
throw new Exception($"{nameof(Application.Current.MainWindow)} must have a root layout panel of type {nameof(Grid)} in order to use attachable Flyout.");
}
void buttonClick(object sender, RoutedEventArgs routedEventArgs)
{
newPopup.IsOpen = true;
}
}
})));
}
}
And then I have any UserControl I create inherit from PopupBase so my Popup UI's look sort of like this:
<local:PopupBase x:Class="RelayC.AddServer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:RelayC"
mc:Ignorable="d"
Width="500"
Height="375">
UI CONTENT INSERTED HERE
</local:PopupBase>
The problem is when I want to attach it to a button, I have to do something like this:
<Window.Resources>
<local:AddServer Grid.Row="1" x:Key="ISERCpm"/>
</Window.Resources>
in my MainWindow.xaml and then call it from a button using
<Button local:PopupAttach.Popup="{StaticResource ISERCpm}" />
Is there anyway to skip the Window Resource declaration and straight just call my PopupBase User Control?
PopupAttach.Popup is an attached dependency property. It is possible to use property element syntax instead of attribute syntax:
<Button>
<local:PopupAttach.Popup>
<local:AddServer Grid.Row="1"/>
</local:PopupAttach.Popup>
</Button>
How could I access a XAML object in my ViewModel? I am really confused. I want to access the <Controls:ModalContentPresenter> object. How could I realise this in a MVVM conform way? On this object I want to call a method ShowModalContent
<Controls:ModalContentPresenter x:Name="modalContent">
<ScrollViewer Behaviors:AdvancedZooming.KeepInCenter="true" Visibility="{Binding LeerformularIsVisible}" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Viewbox Stretch="Uniform">
<Grid>
<DataGrid BorderBrush="{x:Null}">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding AddFieldDefinitionCommand}" Header="Feld hinterlegen" Icon="pack://application:,,,/Images/Designer/field.png" />
<MenuItem Command="{Binding AddFunctionCommand}" Header="Funktion hinterlegen" Icon="pack://application:,,,/Images/Designer/FI_Taschenmesser_16x16.png" />
<MenuItem Command="{Binding RemoveFieldDefinitionCommand}" Header="Aktuelle Felddefinition entfernen" Icon="pack://application:,,,/Images/Designer/remove_field.png" />
<MenuItem Command="{Binding CutCommand}" Header="Ausschneiden" Icon="pack://application:,,,/Images/Zwischenablage/FI_Ausschneiden_16x16.png" />
<MenuItem Command="{Binding CopyCommand}" Header="Kopieren" Icon="pack://application:,,,/Images/Zwischenablage/FI_Kopieren_16x16.png" />
<MenuItem Command="{Binding PasteCommand}" Header="Einfügen" Icon="pack://application:,,,/Images/Zwischenablage/FI_Einfuegen_16x16.png" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
</Viewbox>
</ScrollViewer>
<Controls:ModalContentPresenter.ModalContent>
<StackPanel>
<TextBlock>Test</TextBlock>
<Button>Hide</Button>
</StackPanel>
</Controls:ModalContentPresenter.ModalContent>
</Controls:ModalContentPresenter>
The code of ModalContentPresenter can be found here:
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
namespace Controls
{
[ContentProperty("Content")]
public class ModalContentPresenter : FrameworkElement
{
#region private fields
private Panel layoutRoot;
private ContentPresenter primaryContentPresenter;
private ContentPresenter modalContentPresenter;
private Border overlay;
private object[] logicalChildren;
private KeyboardNavigationMode cachedKeyboardNavigationMode;
private static readonly TraversalRequest traversalDirection;
#endregion
#region dependency properties
public static readonly DependencyProperty IsModalProperty = DependencyProperty.Register("IsModal", typeof(bool), typeof(ModalContentPresenter),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsModalChanged));
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(ModalContentPresenter),
new UIPropertyMetadata(null, OnContentChanged));
public static readonly DependencyProperty ModalContentProperty = DependencyProperty.Register("ModalContent", typeof(object), typeof(ModalContentPresenter),
new UIPropertyMetadata(null, OnModalContentChanged));
public static readonly DependencyProperty OverlayBrushProperty = DependencyProperty.Register("OverlayBrush", typeof(Brush), typeof(ModalContentPresenter),
new UIPropertyMetadata(new SolidColorBrush(Color.FromArgb(204, 169, 169, 169)), OnOverlayBrushChanged));
public bool IsModal
{
get { return (bool)GetValue(IsModalProperty); }
set { SetValue(IsModalProperty, value); }
}
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public object ModalContent
{
get { return (object)GetValue(ModalContentProperty); }
set { SetValue(ModalContentProperty, value); }
}
public Brush OverlayBrush
{
get { return (Brush)GetValue(OverlayBrushProperty); }
set { SetValue(OverlayBrushProperty, value); }
}
#endregion
#region routed events
public static readonly RoutedEvent PreviewModalContentShownEvent = EventManager.RegisterRoutedEvent("PreviewModalContentShown", RoutingStrategy.Tunnel,
typeof(RoutedEventArgs), typeof(ModalContentPresenter));
public static readonly RoutedEvent ModalContentShownEvent = EventManager.RegisterRoutedEvent("ModalContentShown", RoutingStrategy.Bubble,
typeof(RoutedEventArgs), typeof(ModalContentPresenter));
public static readonly RoutedEvent PreviewModalContentHiddenEvent = EventManager.RegisterRoutedEvent("PreviewModalContentHidden", RoutingStrategy.Tunnel,
typeof(RoutedEventArgs), typeof(ModalContentPresenter));
public static readonly RoutedEvent ModalContentHiddenEvent = EventManager.RegisterRoutedEvent("ModalContentHidden", RoutingStrategy.Bubble,
typeof(RoutedEventArgs), typeof(ModalContentPresenter));
public event RoutedEventHandler PreviewModalContentShown
{
add { AddHandler(PreviewModalContentShownEvent, value); }
remove { RemoveHandler(PreviewModalContentShownEvent, value); }
}
public event RoutedEventHandler ModalContentShown
{
add { AddHandler(ModalContentShownEvent, value); }
remove { RemoveHandler(ModalContentShownEvent, value); }
}
public event RoutedEventHandler PreviewModalContentHidden
{
add { AddHandler(PreviewModalContentHiddenEvent, value); }
remove { RemoveHandler(PreviewModalContentHiddenEvent, value); }
}
public event RoutedEventHandler ModalContentHidden
{
add { AddHandler(ModalContentHiddenEvent, value); }
remove { RemoveHandler(ModalContentHiddenEvent, value); }
}
#endregion
#region ModalContentPresenter implementation
static ModalContentPresenter()
{
traversalDirection = new TraversalRequest(FocusNavigationDirection.First);
}
public ModalContentPresenter()
{
layoutRoot = new ModalContentPresenterPanel();
primaryContentPresenter = new ContentPresenter();
modalContentPresenter = new ContentPresenter();
overlay = new Border();
AddVisualChild(layoutRoot);
logicalChildren = new object[2];
overlay.Background = OverlayBrush;
overlay.Child = modalContentPresenter;
overlay.Visibility = Visibility.Hidden;
layoutRoot.Children.Add(primaryContentPresenter);
layoutRoot.Children.Add(overlay);
}
public void ShowModalContent()
{
if (!IsModal)
IsModal = true;
}
public void HideModalContent()
{
if (IsModal)
IsModal = false;
}
private void RaiseModalContentShownEvents()
{
RoutedEventArgs args = new RoutedEventArgs(PreviewModalContentShownEvent);
OnPreviewModalContentShown(args);
if (!args.Handled)
{
args = new RoutedEventArgs(ModalContentShownEvent);
OnModalContentShown(args);
}
}
private void RaiseModalContentHiddenEvents()
{
RoutedEventArgs args = new RoutedEventArgs(PreviewModalContentHiddenEvent);
OnPreviewModalContentHidden(args);
if (!args.Handled)
{
args = new RoutedEventArgs(ModalContentHiddenEvent);
OnModalContentHidden(args);
}
}
protected virtual void OnPreviewModalContentShown(RoutedEventArgs e)
{
RaiseEvent(e);
}
protected virtual void OnModalContentShown(RoutedEventArgs e)
{
RaiseEvent(e);
}
protected virtual void OnPreviewModalContentHidden(RoutedEventArgs e)
{
RaiseEvent(e);
}
protected virtual void OnModalContentHidden(RoutedEventArgs e)
{
RaiseEvent(e);
}
#endregion
#region property changed callbacks
private static void OnIsModalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ModalContentPresenter control = (ModalContentPresenter)d;
if ((bool)e.NewValue == true)
{
control.cachedKeyboardNavigationMode = KeyboardNavigation.GetTabNavigation(control.primaryContentPresenter);
KeyboardNavigation.SetTabNavigation(control.primaryContentPresenter, KeyboardNavigationMode.None);
control.overlay.Visibility = Visibility.Visible;
control.overlay.MoveFocus(traversalDirection);
control.RaiseModalContentShownEvents();
}
else
{
control.overlay.Visibility = Visibility.Hidden;
KeyboardNavigation.SetTabNavigation(control.primaryContentPresenter, control.cachedKeyboardNavigationMode);
control.primaryContentPresenter.MoveFocus(traversalDirection);
control.RaiseModalContentHiddenEvents();
}
}
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ModalContentPresenter control = (ModalContentPresenter)d;
if (e.OldValue != null)
control.RemoveLogicalChild(e.OldValue);
control.primaryContentPresenter.Content = e.NewValue;
control.AddLogicalChild(e.NewValue);
control.logicalChildren[0] = e.NewValue;
}
private static void OnModalContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ModalContentPresenter control = (ModalContentPresenter)d;
if (e.OldValue != null)
control.RemoveLogicalChild(e.OldValue);
control.modalContentPresenter.Content = e.NewValue;
control.AddLogicalChild(e.NewValue);
control.logicalChildren[1] = e.NewValue;
}
private static void OnOverlayBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ModalContentPresenter control = (ModalContentPresenter)d;
control.overlay.Background = (Brush)e.NewValue;
}
#endregion
#region FrameworkElement overrides
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index > 1)
throw new ArgumentOutOfRangeException("index");
return layoutRoot;
}
protected override int VisualChildrenCount
{
get { return 1; }
}
protected override IEnumerator LogicalChildren
{
get { return logicalChildren.GetEnumerator(); }
}
protected override Size ArrangeOverride(Size finalSize)
{
layoutRoot.Arrange(new Rect(finalSize));
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
layoutRoot.Measure(availableSize);
return layoutRoot.DesiredSize;
}
#endregion
#region layout panel
class ModalContentPresenterPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
Size resultSize = new Size(0, 0);
foreach (UIElement child in Children)
{
child.Measure(availableSize);
resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
}
return resultSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement child in InternalChildren)
{
child.Arrange(new Rect(finalSize));
}
return finalSize;
}
}
#endregion
}
}
You shouldn't do this (at least, this is definitely not a MVVM-way).
Use IsModal property and data binding instead:
<Controls:ModalContentPresenter x:Name="modalContent" IsModal="{Binding IsModal}">
where IsModal in the binding expression is a bound data property within your view model.
In MVVM, any data going back and forth should be done via data binding properties on your View Model. That includes virtually any properties of the ContentPresenter - just add a binding. However, if you want call a method of a XAML object in an MVVM-friendly way, you can use an Action.
Let's suppose I wanted to set the focus on a textbox via the view model (contrived example, I know there's other MVVM-ways to do this- just wanted an example that requires a child object of the view).
First, give the textbox that should get the focus a name in your XAML, i.e.,
<TextBox x:Name="textBox1"/>
On your view model, add a property for the Action:
public Action FocusAction {get;set;}
Some time before or as your view is loading, get your DataContext (i.e., your view model) and add the Action to the code behind (the view's .cs file):
ViewModel vm = (ViewModel)this.DataContext;
if ( vm.FocusAction == null )
vm.FocusAction= new Action(() => this.textBox1.Focus());
For instance, you might implement the IsLoaded event and do it there. You could also do this in your constructor after InitializeComponent, as long as it either created your view model instance or it was passed in as a paramater. The key is that the view model is already instantiated and assigned to the view's data context.
Then in your view model, wherever you wanted to add focus to that textbox, call:
FocusAction();
Since what I just showed above requires that your view cast the DataContext to a particular view model, what I do is create an interface for the kinds of actions I need, like this:
interface IFocusable
{
Action FocusAction {get;set;}
}
Then I make my view model implement that inteface. With that done, in my view's code-behind, I can say something like:
if(this.DataContext is IFocusable)
((IFocusable)this.DataContext).FocusAction = new Action(() => this.textBox1.Focus());
I think that makes it more MVVM-compliant, since it's not tightly-coupled to a particular view model, the view just knows it can add an action if the view model is the type of view model that can use it.
More details and another example available here: http://jkshay.com/closing-a-wpf-window-using-mvvm-and-minimal-
code-behind/
I know it's been few years but I just faced the same, so here's my answer in case someone will find it useful...
In your xaml file refer to your ViewModel class in the DataContext:
<Window.DataContext>
<local:YourViewModel x:Name="yourViewModel"/>
</Window.DataContext>
In your ViewModel class create a public function with your xaml file as argument and a private member to hold it:
private MainWindow mainWindow;
public void OnViewInitialized(MainWindow mainWindow)
{
this.mainWindow = mainWindow;
}
In your code behind use the function pass the the 'yourViewModel' you defined in the xaml:
public MainWindow()
{
InitializeComponent();
yourViewModel.OnViewInitialized(this);
}
And that's it :)
Now you can access all your xaml elements from your ViewModel by using your mainWindow member, just give them a name.
mainWindow.textBox1
you can create a constructor for view model which accepts the ContentPage as its parameter.
public class ViewModelClass
{
public ViewModelClass(ContentPage p=null)
{...}
}
then set the binding context in Contentpage back code script passing the referenc of contentpage to viewmodel
public class ContentPageClass : ContentPage
{
public ContentPageClass()
{
BindingContext = new ViewModelClass(p:this);
}
}
I have a custom control that has a DependencyProperty of type ObservableCollection that is bound to an observableCollection:
<MyControl MyCollectionProperty = {Binding MyObservableCollection} ...
Problem is adding to MyObservableCollection does not update MyCollectionProperty.
I need to completly replace the MyObservableCollection to make it work e.g.
MyObservableCollection = null;
MyObservableCollection = new ObservableCollection(){...}
Is there a better way to deal with this?
EDIT:
public ObservableCollection<string> Columns
{
get { return (ObservableCollection<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
In addition to what grantz has answered, I would suggest to declare the property with type IEnumerable<string> and check at runtime if the collection object implements the INotifyCollectionChanged interface. This provides greater flexibility as to which concrete collection implementation may be used as property value. A user may then decide to have their own specialized implementation of an observable collection.
Note also that in the ColumnsPropertyChanged callback the CollectionChanged event handler is attached to the new collection, but also removed from the old one.
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
"Columns", typeof(IEnumerable<string>), typeof(MyControl),
new PropertyMetadata(null, ColumnsPropertyChanged));
public IEnumerable<string> Columns
{
get { return (IEnumerable<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
private static void ColumnsPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control= (MyControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.ColumnsCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += control.ColumnsCollectionChanged;
}
control.UpdateColumns();
}
private void ColumnsCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
// optionally take e.Action into account
UpdateColumns();
}
private void UpdateColumns()
{
...
}
Below is a working example that may help.
In this example, the method OnChanged is called immediately, when the Add button is clicked "Changed" is written to the console.
The Control
public class MyControl : Control
{
public ObservableCollection<string> ExtraColumns
{
get { return (ObservableCollection<string>)GetValue(ExtraColumnsProperty); }
set { SetValue(ExtraColumnsProperty, value); }
}
public static readonly DependencyProperty ExtraColumnsProperty =
DependencyProperty.Register("ExtraColumns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
static void OnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as MyControl).OnChanged();
}
void OnChanged()
{
if ( ExtraColumns != null )
ExtraColumns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ExtraColumns_CollectionChanged);
}
void ExtraColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("Changed");
}
}
The Window
<Window x:Class="WpfApplication18.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication18"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:MyControl ExtraColumns="{Binding Extras}"/>
<Button Click="Button_Click">Add</Button>
</StackPanel>
</Window>
Window Code Behind
public partial class MainWindow : Window
{
private ObservableCollection<string> _extras = new ObservableCollection<string>( );
public ObservableCollection<string> Extras
{
get { return _extras; }
set
{
if (value != _extras)
{
_extras = value;
}
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Extras.Add("Additional");
}
}
I have a View and a ViewModel in Silverlight 3.0.
The view contains a standard ScrollViewer, which contains dynamic content.
Depending on the content within the ScrollViewer, the user could have scrolled half way down the content, and then performed an action that causes the ScrollViewer to load new content, but the ScrollViewer does not automatically scroll to the top.
I want to be able to bind to the VerticalOffset property, but it is read-only. Any ideas on attachable behavior?
Any ideas?
Thanks.
The following blog post provides an attached behaviour that exposes the vertical / horizontal offsets of a scrollviewer so that you can bind to them, or set them in code:
http://blog.scottlogic.com/2010/07/21/exposing-and-binding-to-a-silverlight-scrollviewers-scrollbars.html
This allows the following markup:
<ScrollViewer
local:ScrollViewerBinding.VerticalOffset="{Binding YPosition, Mode=TwoWay}"
local:ScrollViewerBinding.HorizontalOffset="{Binding XPosition, Mode=TwoWay}">
<!-- Big content goes here! -->
</ScrollViewer>
Since you are using a ViewModel I take it the "action that causes ScrollViewer to load new content" is a result of changes made inside or to the ViewModel. That being the case I would add an event to the ViewModel that gets fired every time such a change occurs.
Your View can the add a handler on this event and call ScrollToVerticalPosition on the ScrollViewer when its fired.
I've simplified the #ColinE's solution. Instead of hooking to the ScrollBar.ValueChanged event, I hook to the ScrollViewer.ScrollChanged event. So, 1. it is not necessary to find the ScrollBar in the visual tree and 2. ScrollBar.ValueChanged is called in some transition states when the content of the ScrollViewer changes and I do not want to catch these states.
I post my code for the VerticalOffset, the HorizontalOffset is similar:
/// <summary>
/// VerticalOffset attached property
/// </summary>
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset", typeof(double),
typeof(ScrollViewerBinding), new FrameworkPropertyMetadata(double.NaN,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnVerticalOffsetPropertyChanged));
/// <summary>
/// Just a flag that the binding has been applied.
/// </summary>
private static readonly DependencyProperty VerticalScrollBindingProperty =
DependencyProperty.RegisterAttached("VerticalScrollBinding", typeof(bool?), typeof(ScrollViewerBinding));
public static double GetVerticalOffset(DependencyObject depObj)
{
return (double)depObj.GetValue(VerticalOffsetProperty);
}
public static void SetVerticalOffset(DependencyObject depObj, double value)
{
depObj.SetValue(VerticalOffsetProperty, value);
}
private static void OnVerticalOffsetPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = d as ScrollViewer;
if (scrollViewer == null)
return;
BindVerticalOffset(scrollViewer);
scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
public static void BindVerticalOffset(ScrollViewer scrollViewer)
{
if (scrollViewer.GetValue(VerticalScrollBindingProperty) != null)
return;
scrollViewer.SetValue(VerticalScrollBindingProperty, true);
scrollViewer.ScrollChanged += (s, se) =>
{
if (se.VerticalChange == 0)
return;
SetVerticalOffset(scrollViewer, se.VerticalOffset);
};
}
And use it in the XAML:
<ScrollViewer local:ScrollViewerBinding.VerticalOffset="{Binding ScrollVertical}">
<!-- content ... -->
</ScrollViewer>
I started with this but noticed there isn't a cleanup phase so here goes the complete implementation with both horizontal and vertical offsets bindable:
using System.Windows;
using System.Windows.Controls;
namespace Test
{
public static class ScrollPositionBehavior
{
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.RegisterAttached(
"HorizontalOffset",
typeof(double),
typeof(ScrollPositionBehavior),
new FrameworkPropertyMetadata(
double.NaN,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnHorizontalOffsetPropertyChanged));
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached(
"VerticalOffset",
typeof(double),
typeof(ScrollPositionBehavior),
new FrameworkPropertyMetadata(
double.NaN,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnVerticalOffsetPropertyChanged));
private static readonly DependencyProperty IsScrollPositionBoundProperty =
DependencyProperty.RegisterAttached("IsScrollPositionBound", typeof(bool?), typeof(ScrollPositionBehavior));
public static void BindOffset(ScrollViewer scrollViewer)
{
if (scrollViewer.GetValue(IsScrollPositionBoundProperty) is true)
return;
scrollViewer.SetValue(IsScrollPositionBoundProperty, true);
scrollViewer.Loaded += ScrollViewer_Loaded;
scrollViewer.Unloaded += ScrollViewer_Unloaded;
}
public static double GetHorizontalOffset(DependencyObject depObj)
{
return (double)depObj.GetValue(HorizontalOffsetProperty);
}
public static double GetVerticalOffset(DependencyObject depObj)
{
return (double)depObj.GetValue(VerticalOffsetProperty);
}
public static void SetHorizontalOffset(DependencyObject depObj, double value)
{
depObj.SetValue(HorizontalOffsetProperty, value);
}
public static void SetVerticalOffset(DependencyObject depObj, double value)
{
depObj.SetValue(VerticalOffsetProperty, value);
}
private static void OnHorizontalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = d as ScrollViewer;
if (scrollViewer == null || double.IsNaN((double)e.NewValue))
return;
BindOffset(scrollViewer);
scrollViewer.ScrollToHorizontalOffset((double)e.NewValue);
}
private static void OnVerticalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = d as ScrollViewer;
if (scrollViewer == null || double.IsNaN((double)e.NewValue))
return;
BindOffset(scrollViewer);
scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
private static void ScrollChanged(object s, ScrollChangedEventArgs se)
{
if (se.VerticalChange != 0)
SetVerticalOffset(s as ScrollViewer, se.VerticalOffset);
if (se.HorizontalChange != 0)
SetHorizontalOffset(s as ScrollViewer, se.HorizontalOffset);
}
private static void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
var scrollViewer = sender as ScrollViewer;
scrollViewer.ScrollChanged += ScrollChanged;
}
private static void ScrollViewer_Unloaded(object sender, RoutedEventArgs e)
{
var scrollViewer = sender as ScrollViewer;
scrollViewer.SetValue(IsScrollPositionBoundProperty, false);
scrollViewer.ScrollChanged -= ScrollChanged;
scrollViewer.Loaded -= ScrollViewer_Loaded;
scrollViewer.Unloaded -= ScrollViewer_Unloaded;
}
}
}