WPF FlowDocumentScrollViewer automatically scroll to bottom of the document - c#

I have a UserControl that the XAML looks like:
<Grid>
<FlowDocumentScrollViewer Name="ProvisionStatusMonitor" Document="{Binding Document}" SourceUpdated="OnSourceUpdated"/>
</Grid>
The code-behind looks like
private void OnSourceUpdated(object sender, DataTransferEventArgs e)
{
FlowDocumentScrollViewer docViewer = (FlowDocumentScrollViewer) sender;
var scrollViewer = (ScrollViewer)docViewer.Template
.FindName("ProvisionStatusMonitor", docViewer);
scrollViewer.ScrollToEnd();
}
But this doesn't seem to work. When the document is changed I was thinking that this event should fire and I could automatically scroll to the bottom of the document. What am I missing?

You may attach a PropertyChanged event handler to the view model in a DataContextChanged handler in the view.
Assuming that StatusView is a UserControl that contains the FlowDocumentScrollViewer shown in the question, it could look like this:
public StatusView()
{
InitializeComponent();
DataContextChanged += StatusViewDataContextChanged;
}
private void StatusViewDataContextChanged(
object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is INotifyPropertyChanged oldViewModel)
{
oldViewModel.PropertyChanged -= ViewModelPropertyChanged;
}
if (e.NewValue is INotifyPropertyChanged newViewModel)
{
newViewModel.PropertyChanged += ViewModelPropertyChanged;
}
}
private void ViewModelPropertyChanged(
object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Document")
{
var docViewer = ProvisionStatusMonitor;
var scrollViewer = (ScrollViewer)docViewer.Template
.FindName("PART_ContentHost", docViewer);
scrollViewer.ScrollToEnd();
}
}

Related

ListBox SourceUpdated's event handler is not called in MVVM

I want to run event handler when DeviceListItem is updated. But, The evnet handler is not called even though data is updated on view.
XAML
<ListBox x:Name="DeviceListItem" ItemsSource="{Binding DeviceListItems,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SourceUpdated="OnDeviceListItemsUpdated">
View
private void OnDeviceListItemsUpdated(object sender, EventArgs e)
{
// to do
}
ViewModel
private ObservableCollection<Device> mDeviceListItems;
public ObservableCollection<Device> DeviceListItems
{
get { return mDeviceListItems; }
set { mDeviceListItems = value;
RaisePropertyChangedEvent("DeviceListItems"); }
}
I think you can use CollectionChanged of ObservableCollection
DeviceListItems.CollectionChanged += itemDisplayList_CollectionChanged;
void itemDisplayList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
}
Simply, I solved it without sourceUpdated event handler.
INotifyPropertyChanged viewModel = (INotifyPropertyChanged)DataContext;
viewModel.PropertyChanged += OnDeviceListItemsUpdated;
}
private void OnDeviceListItemsUpdated(object sender, PropertyChangedEventArgs e)

How to fill ContextMenuStrip in XAML?

I've created WPF application with NotifyIcon to work in tray.
public partial class MainWindow : Window
{
public NotifyIcon NotifyIcon { get; } = new NotifyIcon
{
Icon = Properties.Resources.status_on_ico,
Visible = true
};
public MainWindow()
{
InitializeComponent();
NotifyIcon.ContextMenuStrip = MyContextMenuStrip;
NotifyIcon.Click += NotifyIcon_Click;
}
private void Window_Closing(object sender, CancelEventArgs e)
{
e.Cancel = true;
WindowState = WindowState.Minimized;
}
private void NotifyIcon_Click(object sender, EventArgs e)
{
Show();
}
}
XAML of main window is nothing special and not relevant.
I want to create ContextMenuStrip of NotifyIcon in XAML (I know how to do it code behind but don't want it).
Here is what I've managed.
<WindowsFormsHost>
<wf:ContextMenuStrip x:Name="MyContextMenuStrip" TopLevel="False">
<wf:ContextMenuStrip.Items>
<!-- How to add items here? -->
</wf:ContextMenuStrip.Items>
</wf:ContextMenuStrip>
</WindowsFormsHost>
The question is how to add items to ContextMenuStrip.Items with Name and Click event handlers in XAML?
The question is how to add items to ContextMenuStrip.Items with Name and Click event handlers in XAML?
Try this:
<WindowsFormsHost>
<wf:ContextMenuStrip x:Name="MyContextMenuStrip" TopLevel="False">
<wf:ContextMenuStrip.Items>
<wf:ToolStripMenuItem Text="test1" Click="It_Click" />
<wf:ToolStripMenuItem Text="test2" />
</wf:ContextMenuStrip.Items>
</wf:ContextMenuStrip>
</WindowsFormsHost>
private void It_Click(object sender, EventArgs e)
{
MessageBox.Show("click!");
}

Add to Favorite a Button inside a StackPanel

I have a problem and nothing come to my mind how to solve this.
I need to add a button to Favorites in a StackPanel, the problem is that the button is pressed on other page that is StackPanel declared and I can't Add button to it's Children.
So, I have 2 pages: MainPage.xaml and PlayerPage.xaml .
In MainPage I keep a StackPanel with Buttons like:
<StackPanel x:Name="mainStack" >
<Button x:Name="but1" Click="Button2_Click" Tag="/videos/video1.mp4" Content="Play Video 1" />
<Button x:Name="but2" Click="Button2_Click" Tag="/videos/video2.mp4" Content="Play Video 2" />
<Button x:Name="but3" Click="Button2_Click" Tag="/videos/video3.mp4" Content="Play Video 3" />
</StackPanel>
<StackPanel x:Name="favoriteStack" >
<!-- Here need to be added favorite videos when user press Add to fav button! -->
</StackPanel>
.cs
private void Button2_Click(object sender, EventArgs e)
{
NavigationService.Navigate(
new Uri("/PlayerPage.xaml?path=" +
HttpUtility.UrlEncode((sender as Button).Tag.ToString()),
UriKind.Relative));
}
In PlayerPage.xaml :
<MediaElement x:Name="mediaElement1"
MediaOpened="mediaElement1_MediaOpened"
MediaFailed="mediaElement1_MediaFailed"
MediaEnded="mediaElement1_MediaEnded"
CurrentStateChanged="mediaElement1_CurrentStateChanged" />
<Button x:Name="AddToFav" Click="Button1_Click"
Content="Add this video to Favorites" />
.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (NavigationContext.QueryString.TryGetValue("path", out path))
{
if (!string.IsNullOrWhiteSpace(path))
{
mediaElement1.Source = new Uri( path );
}
}
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
// here must be somethink like:
Button butsender = new Button();
butsender = sender as Button;
stack2.Children.Add(butsender);
//better will be to save to Isolated Storage or somethink like this for future launching...
}
I have a lot of problems because I don't really get it how to perform that... I've tried to use global App Bar and allot more but unsuccesfully. Any help would be appreciated! Thanks!
The best way to do this is to follow the MVVM Pattern. You'll have a ViewModel class defined containing two lists.
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Video> Videos { get; set; }
public ObservableCollection<Video> FavoriteVideos { get; set; }
public MainViewModel()
{
Videos = new ObservableCollection<Video>();
FavoriteVideos = new ObservableCollection<Video>();
}
private Video selectedVideo;
public Video SelectedVideo
{
get { return selectedVideo; }
set { selectedVideo = value; NotifyPropertyChanged("SelectedVideo"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In App.xaml.cs you'll define the ViewModel
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
Then you can set the data context of both pages (MainPage and ther other) in the constructor like this:
DataContext = App.ViewModel;
Now you'll only have to bind the items to both lists (StackPanel won't do here. Use other listing like ListBox)
ItemsSource="{Binding Videos}"
ItemsSource="{Biding FavoriteVideos}"
For the navigation if you use ListBox just set the click event
ItemClick="ItemClick"
private void ItemClick(object sender, ItemClickEventArgs e)
{
Video item = e.ClickedItem as Video;
App.ViewModel.SelectedVideo = item;
NavigationService.Navigate(new Uri("/PlayerPage.xaml,UriKind.Relative));
}
At the PlayerPage just bind to SelectedVideo and you're good. For favorite you just have to add the video to the FavoriteList
private void FavoriteButton_Click(object sender, RoutedEventArgs e)
{
if(!App.ViewModel.FavoriteVideos.Contains(App.ViewModel.SelectedVideo)) {
App.ViewModel.FavoriteVideos.Add(App.ViewModel.SelectedVideo);
}
}
Now you can dynamically add data in your app if needed. StackPanel will only help with your static videos and it's not practical to use if the data changes.

Close Window without code-behind in WPF

Is it possible to bind a Button to Close the Window without adding a code-behind event?
<Button Content="OK" Command="{Binding CloseWithSomeKindOfTrick}" />
Instead of the following XAML:
<Button Content="OK" Margin="0,8,0,0" Click="Button_Click">
With the code-behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
Close();
}
Thanks!
If you want close the dialog Window, you can add for Button IsCancel property:
<Button Name="CloseButton"
IsCancel="True" ... />
This means the following MSDN:
When you set the IsCancel property of a Button to true, you create a Button that is registered with the AccessKeyManager. The button is then activated when a user presses the ESC key.
Now, if you click on this Button, or press Esc then dialog Window is closing, but it does not work for the normal MainWindow.
To close the MainWindow, you can simply add a Click handler which has already been shown. But if you want a more elegant solution that would satisfy the MVVM style you can add the attached behavior:
public static class ButtonBehavior
{
#region Private Section
private static Window MainWindow = Application.Current.MainWindow;
#endregion
#region IsCloseProperty
public static readonly DependencyProperty IsCloseProperty;
public static void SetIsClose(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsCloseProperty, value);
}
public static bool GetIsClose(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsCloseProperty);
}
static ButtonBehavior()
{
IsCloseProperty = DependencyProperty.RegisterAttached("IsClose",
typeof(bool),
typeof(ButtonBehavior),
new UIPropertyMetadata(false, IsCloseTurn));
}
#endregion
private static void IsCloseTurn(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
if (MainWindow != null)
MainWindow.PreviewKeyDown += new KeyEventHandler(MainWindow_PreviewKeyDown);
var button = sender as Button;
if (button != null)
button.Click += new RoutedEventHandler(button_Click);
}
}
private static void button_Click(object sender, RoutedEventArgs e)
{
MainWindow.Close();
}
private static void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
MainWindow.Close();
}
}
And in MainWindow use this Behavior like as:
<Window x:Class="MyProjectNamespace.MainWindow"
xmlns:local="clr-namespace:MyProjectNamespace">
<Button Name="CloseButton"
local:ButtonBehavior.IsClose="True" ... />

"UpdateSourceTrigger=PropertyChanged" equivalent for a Windows Phone 7 TextBox

Is there a way to get a TextBox in Windows Phone 7 to update the Binding as the user types each letter rather than after losing focus?
Like the following WPF TextBox would do:
<TextBox Text="{Binding Path=TextProperty, UpdateSourceTrigger=PropertyChanged}"/>
Silverlight for WP7 does not support the syntax you've listed. Do the following instead:
<TextBox TextChanged="OnTextBoxTextChanged"
Text="{Binding MyText, Mode=TwoWay,
UpdateSourceTrigger=Explicit}" />
UpdateSourceTrigger = Explicit is a smart bonus here. What is it? Explicit: Updates the binding source only when you call the UpdateSource method. It saves you one extra binding set when the user leaves the TextBox.
In C#:
private void OnTextBoxTextChanged( object sender, TextChangedEventArgs e )
{
TextBox textBox = sender as TextBox;
// Update the binding source
BindingExpression bindingExpr = textBox.GetBindingExpression( TextBox.TextProperty );
bindingExpr.UpdateSource();
}
I like using an attached property. Just in case you're into those little buggers.
<toolkit:DataField Label="Name">
<TextBox Text="{Binding Product.Name, Mode=TwoWay}" c:BindingUtility.UpdateSourceOnChange="True"/>
</toolkit:DataField>
And then the backing code.
public class BindingUtility
{
public static bool GetUpdateSourceOnChange(DependencyObject d)
{
return (bool)d.GetValue(UpdateSourceOnChangeProperty);
}
public static void SetUpdateSourceOnChange(DependencyObject d, bool value)
{
d.SetValue(UpdateSourceOnChangeProperty, value);
}
// Using a DependencyProperty as the backing store for …
public static readonly DependencyProperty
UpdateSourceOnChangeProperty =
DependencyProperty.RegisterAttached(
"UpdateSourceOnChange",
typeof(bool),
typeof(BindingUtility),
new PropertyMetadata(false, OnPropertyChanged));
private static void OnPropertyChanged (DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var textBox = d as TextBox;
if (textBox == null)
return;
if ((bool)e.NewValue)
{
textBox.TextChanged += OnTextChanged;
}
else
{
textBox.TextChanged -= OnTextChanged;
}
}
static void OnTextChanged(object s, TextChangedEventArgs e)
{
var textBox = s as TextBox;
if (textBox == null)
return;
var bindingExpression = textBox.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null)
{
bindingExpression.UpdateSource();
}
}
}
Not through binding syntax, no, but it's easy enough without. You have to handle the TextChanged event and call UpdateSource on the binding.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
((TextBox) sender).GetBindingExpression( TextBox.TextProperty ).UpdateSource();
}
This can be converted into an attached behavior as well pretty easily.
In TextChanged event call UpdateSource().
BindingExpression be = itemNameTextBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
You can write your own TextBox Behavior to handle Update on TextChanged:
This is my sample to PasswordBox but you can simple change it to handle any property of the any object.
public class UpdateSourceOnPasswordChangedBehavior
: Behavior<PasswordBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PasswordChanged += OnPasswordChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PasswordChanged -= OnPasswordChanged;
}
private void OnPasswordChanged(object sender, RoutedEventArgs e)
{
AssociatedObject.GetBindingExpression(PasswordBox.PasswordProperty).UpdateSource();
}
}
Ussage:
<PasswordBox x:Name="Password" Password="{Binding Password, Mode=TwoWay}" >
<i:Interaction.Behaviors>
<common:UpdateSourceOnPasswordChangedBehavior/>
</i:Interaction.Behaviors>
</PasswordBox>
UpdateSourceTrigger=Explicit doesnt work for me, hence Im using custom class derivated from TextBox
public class TextBoxEx : TextBox
{
public TextBoxEx()
{
TextChanged += (sender, args) =>
{
var bindingExpression = GetBindingExpression(TextProperty);
if (bindingExpression != null)
{
bindingExpression.UpdateSource();
}
};
}
}
It's just one line of code!
(sender as TextBox).GetBindingExpression(TextBox.TextProperty).UpdateSource();
You can create a generic TextChanged event (for example "ImmediateTextBox_TextChanged") in the code behind of your page, and than link it to any TextBox in the page.
I took Praetorian's answer and made an extension class that inherits TextBox so you don't have to muddle up your view's code behind with this behavior.
C-Sharp:
public class TextBoxUpdate : TextBox
{
public TextBoxUpdate()
{
TextChanged += OnTextBoxTextChanged;
}
private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
TextBox senderText = (TextBox)sender;
BindingExpression bindingExp = senderText.GetBindingExpression(TextBox.TextProperty);
bindingExp.UpdateSource();
}
}
VisualBasic:
Public Class TextBoxUpdate : Inherits TextBox
Private Sub OnTextBoxTextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged
Dim senderText As TextBox = DirectCast(sender, TextBox)
Dim bindingExp As BindingExpression = senderText.GetBindingExpression(TextBox.TextProperty)
bindingExp.UpdateSource()
End Sub
End Class
Then call like this in XAML:
<local:TextBoxUpdate Text="{Binding PersonName, Mode=TwoWay}"/>

Categories

Resources