Automatic vertical scroll bar in WPF TextBlock? - c#

I have a TextBlock in WPF. I write many lines to it, far exceeding its vertical height. I expected a vertical scroll bar to appear automatically when that happens, but it didn't. I tried to look for a scroll bar property in the Properties pane, but could not find one.
How can I make vertical scroll bar created automatically for my TextBlock once its contents exceed its height?
Clarification: I would rather do it from the designer and not by directly writing to the XAML.

Wrap it in a scroll viewer:
<ScrollViewer>
<TextBlock />
</ScrollViewer>
NOTE this answer applies to a TextBlock (a read-only text element) as asked for in the original question.
If you want to show scroll bars in a TextBox (an editable text element) then use the ScrollViewer attached properties:
<TextBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto" />
Valid values for these two properties are Disabled, Auto, Hidden and Visible.

can use the following now:
<TextBox Name="myTextBox"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True">SOME TEXT
</TextBox>

Something better would be:
<Grid Width="Your-specified-value" >
<ScrollViewer>
<TextBlock Width="Auto" TextWrapping="Wrap" />
</ScrollViewer>
</Grid>
This makes sure that the text in your textblock does not overflow and overlap the elements below the textblock as may be the case if you do not use the grid. That happened to me when I tried other solutions even though the textblock was already in a grid with other elements. Keep in mind that the width of the textblock should be Auto and you should specify the desired with in the Grid element. I did this in my code and it works beautifully.
HTH.

<ScrollViewer MaxHeight="50"
Width="Auto"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<TextBlock Text="{Binding Path=}"
Style="{StaticResource TextStyle_Data}"
TextWrapping="Wrap" />
</ScrollViewer>
I am doing this in another way by putting MaxHeight in ScrollViewer.
Just Adjust the MaxHeight to show more or fewer lines of text. Easy.

<ScrollViewer Height="239" VerticalScrollBarVisibility="Auto">
<TextBox AcceptsReturn="True" TextWrapping="Wrap" LineHeight="10" />
</ScrollViewer>
This is way to use the scrolling TextBox in XAML and use it as a text area.

You can use
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollBarVisibility="Visible"
These are attached property of wpf.
For more information
http://wpfbugs.blogspot.in/2014/02/wpf-layout-controls-scrollviewer.html

This answer describes a solution using MVVM.
This solution is great if you want to add a logging box to a window, that automatically scrolls to the bottom each time a new logging message is added.
Once these attached properties are added, they can be reused anywhere, so it makes for very modular and reusable software.
Add this XAML:
<TextBox IsReadOnly="True"
Foreground="Gainsboro"
FontSize="13"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True"
attachedBehaviors:TextBoxApppendBehaviors.AppendText="{Binding LogBoxViewModel.AttachedPropertyAppend}"
attachedBehaviors:TextBoxClearBehavior.TextBoxClear="{Binding LogBoxViewModel.AttachedPropertyClear}"
TextWrapping="Wrap">
Add this attached property:
public static class TextBoxApppendBehaviors
{
#region AppendText Attached Property
public static readonly DependencyProperty AppendTextProperty =
DependencyProperty.RegisterAttached(
"AppendText",
typeof (string),
typeof (TextBoxApppendBehaviors),
new UIPropertyMetadata(null, OnAppendTextChanged));
public static string GetAppendText(TextBox textBox)
{
return (string)textBox.GetValue(AppendTextProperty);
}
public static void SetAppendText(
TextBox textBox,
string value)
{
textBox.SetValue(AppendTextProperty, value);
}
private static void OnAppendTextChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs args)
{
if (args.NewValue == null)
{
return;
}
string toAppend = args.NewValue.ToString();
if (toAppend == "")
{
return;
}
TextBox textBox = d as TextBox;
textBox?.AppendText(toAppend);
textBox?.ScrollToEnd();
}
#endregion
}
And this attached property (to clear the box):
public static class TextBoxClearBehavior
{
public static readonly DependencyProperty TextBoxClearProperty =
DependencyProperty.RegisterAttached(
"TextBoxClear",
typeof(bool),
typeof(TextBoxClearBehavior),
new UIPropertyMetadata(false, OnTextBoxClearPropertyChanged));
public static bool GetTextBoxClear(DependencyObject obj)
{
return (bool)obj.GetValue(TextBoxClearProperty);
}
public static void SetTextBoxClear(DependencyObject obj, bool value)
{
obj.SetValue(TextBoxClearProperty, value);
}
private static void OnTextBoxClearPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs args)
{
if ((bool)args.NewValue == false)
{
return;
}
var textBox = (TextBox)d;
textBox?.Clear();
}
}
Then, if you're using a dependency injection framework such as MEF, you can place all of the logging-specific code into it's own ViewModel:
public interface ILogBoxViewModel
{
void CmdAppend(string toAppend);
void CmdClear();
bool AttachedPropertyClear { get; set; }
string AttachedPropertyAppend { get; set; }
}
[Export(typeof(ILogBoxViewModel))]
public class LogBoxViewModel : ILogBoxViewModel, INotifyPropertyChanged
{
private readonly ILog _log = LogManager.GetLogger<LogBoxViewModel>();
private bool _attachedPropertyClear;
private string _attachedPropertyAppend;
public void CmdAppend(string toAppend)
{
string toLog = $"{DateTime.Now:HH:mm:ss} - {toAppend}\n";
// Attached properties only fire on a change. This means it will still work if we publish the same message twice.
AttachedPropertyAppend = "";
AttachedPropertyAppend = toLog;
_log.Info($"Appended to log box: {toAppend}.");
}
public void CmdClear()
{
AttachedPropertyClear = false;
AttachedPropertyClear = true;
_log.Info($"Cleared the GUI log box.");
}
public bool AttachedPropertyClear
{
get { return _attachedPropertyClear; }
set { _attachedPropertyClear = value; OnPropertyChanged(); }
}
public string AttachedPropertyAppend
{
get { return _attachedPropertyAppend; }
set { _attachedPropertyAppend = value; OnPropertyChanged(); }
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Here's how it works:
The ViewModel toggles the Attached Properties to control the TextBox.
As it's using "Append", it's lightning fast.
Any other ViewModel can generate logging messages by calling methods on the logging ViewModel.
As we use the ScrollViewer built into the TextBox, we can make it automatically scroll to the bottom of the textbox each time a new message is added.

Dont know if someone else has this problem but wrapping my TextBlock into a ScrollViewer somewhow messed up my UI - as a simple workaround I figured out that replacing the TextBlock by a TextBox like this one
<TextBox SelectionBrush="Transparent"
Cursor="Arrow"
IsReadOnly="True"
Text="{Binding Text}"
VerticalScrollBarVisibility="Auto">
creates a TextBox that looks and behaves like a TextBlock with a scrollbar (and you can do it all in the designer).

I tried to to get these suggestions to work for a textblock, but couldn't get it to work. I even tried to get it to work from the designer. (Look in Layout and expand the list by clicking the down-arrow "V" at the bottom) I tried setting the scrollviewer to Visible and then Auto, but it still wouldn't work.
I eventually gave up and changed the TextBlock to a TextBox with the Readonly attribute set, and it worked like a charm.

This is a simple solution to that question. The vertical scroll will be activated only when the text overflows.
<TextBox Text="Try typing some text here " ScrollViewer.VerticalScrollBarVisibility="Auto" TextWrapping="WrapWithOverflow" />

Related

PopUp binding not done correctly

I have a listview which on mouse enter to a particular column, i try to launch a popup in viewmodel class by setting isOpen to true in MyAction2() function which gets called on when user enters mouse on that column of listview.
I observe that when the mouse-enter to that column.It calls my function (MyAction2() function in ViewModel, see code written below) but even on setting the isopen variable to true in MyAction2(), The set-get method of binded isOpen not get called. Now i feel there is problem in binding. Which normally should be correct i feel some thing is missing but i dont know what.
My Xaml (containing teh opup and the column in ListView which on mouse enter calls an event called MyAction2() in ViewModel):
<Grid>
<StackPanel>
<Popup Margin="10,10,0,13" Name="Popup1" IsOpen="{Binding PopUpLaunched,Mode=TwoWay}" Placement="Top" PopupAnimation="Fade" StaysOpen="True" HorizontalAlignment="Left" VerticalAlignment="Top" Width="194" Height="200" MinWidth="500" MinHeight="500">
<StackPanel>
<Border Background="Red">
<TextBlock Name="McTextBlock" Background="LightBlue"> This is popup text </TextBlock>
</Border>
</StackPanel>
</Popup>
</StackPanel>
</Grid>
ViewModel.cs
private bool popUpLaunched;
public bool PopUpLaunched {
get {
return popUpLaunched;
} //Get set never gets called even after the popUpLaunched=true in the MyAction2() call
set {
popUpLaunched = value;
OnPropertyChanged("PopUpLaunched");
}
}
private void MyAction2(object param) //The function which gets called on mouse event but do not pop ups the popup
{
popUpLaunched = true;
}
Whats wrong and where is wrong ?
You should set the PopupLaunched property instead of setting the popUpLaunched field for the setter to get called and the PropertyChanged event to get raised:
private void MyAction2(object param)
{
PopUpLaunched = true;
}
In order to implement such a binding, you can make that property a Dependency property like this
public static readonly DependencyProperty PopUpLaunched = DependencyProperty.Register(
"popUpLaunched", typeof(bool), typeof(MainPage), new PropertyMetadata(null));
public bool popUpLaunched
{
get { return (bool)GetValue(PopUpLaunched); }
set { SetValue(PopUpLaunched, value); }
}
If you are not working on the MainPage, change that typeof(MainPage) argument respectively. And adjust getter and setter for your needs.

Controling MediaElement and its source in MVVM pattern

I have DataGrid with list of video clips and MediaElement which should play a clip selected in that DataGrid. I've managed to achieve this by doing this:
MainWindow.xaml
<DataGrid x:Name="dataGrid_Video"
...
SelectedItem="{Binding fosaModel.SelectedVideo}"
SelectionUnit="FullRow"
SelectionMode="Single">
...
<MediaElement x:Name="PlayerWindow" Grid.Column="1" HorizontalAlignment="Left" Height="236" Grid.Row="1" VerticalAlignment="Top" Width="431" Margin="202,0,0,0" Source="{Binding fosaModel.SelectedVideo.fPath}"/>
But then I have no control over playback. So I searched a little bit, and I found this solution, and implemented Play button:
MainWindow.xaml
...
<ContentControl Content="{Binding Player}" Grid.Column="1" HorizontalAlignment="Left" Height="236" Grid.Row="1" VerticalAlignment="Top" Width="431" Margin="202,0,0,0"/>
...
<Button x:Name="playButton" Content="Odtwórz" Grid.Column="1" HorizontalAlignment="Left" Margin="202,273,0,0" Grid.Row="1" VerticalAlignment="Top" Width="75" IsDefault="True" Command="{Binding PlayVideoCommand}"/>
...
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
...
Player = new MediaElement()
{
LoadedBehavior = MediaState.Manual,
};
PlayVideoCommand = new RelayCommand(() => { _playVideoCommand(); });
...
public MediaElement Player{ get; set; }
private void _playVideoCommand()
{
Player.Source = new System.Uri(fosaModel.SelectedVideo.fPath);
Player.Play();
}
fosaModel.cs
public class fosaModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<fosaVideo> ListOfVideos { get; set; } = new ObservableCollection<fosaVideo>();
public ObservableCollection<fosaAudio> ListOfAudios { get; set; } = new ObservableCollection<fosaAudio>();
public fosaVideo SelectedVideo { get; set; }
}
So now selected video is played when I press play button. Changing selection doesn't change the source of MediaElement, like it did earlier. What I want is to have control over playback of video, but also to have selecting other video in DataGrid change the source of MediaElement. Is there anyway to do so without breaking the MVVM pattern?
I'm new to WPF, and I'm feeling like I'm missing something basic. I use MVVM Light and Fody.PropertyChanged.
EDIT:
I went with Peter Moore's answer and Play/Stop feature works like a charm. But now I would like to have control over position of playback of a video. I've made another attached property:
public class MediaElementAttached : DependencyObject
{
...
#region VideoProgress Property
public static DependencyProperty VideoProgressProperty =
DependencyProperty.RegisterAttached(
"VideoProgress", typeof(TimeSpan), typeof(MediaElementAttached),
new PropertyMetadata(new TimeSpan(0,0,0), OnVideoProgressChanged));
public static TimeSpan GetVideoProgress(DependencyObject d)
{
return (TimeSpan)d.GetValue(VideoProgressProperty);
}
public static void SetVideoProgress(DependencyObject d, TimeSpan value)
{
d.SetValue(VideoProgressProperty, value);
}
private static void OnVideoProgressChanged(
DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
MediaElement me = obj as MediaElement;
me.LoadedBehavior = MediaState.Manual;
if (me == null)
return;
TimeSpan videoProgress = (TimeSpan)args.NewValue;
me.Position = videoProgress;
}
#endregion
}
MainWindow.xaml
<MediaElement
...
local:MediaElementAttached.VideoProgress="{Binding fosaModel.videoProgress, Mode=TwoWay}" x:Name="playerWindow" Grid.Column="1" HorizontalAlignment="Left" Height="236" Grid.Row="1" VerticalAlignment="Top" Width="431" Margin="202,0,0,0" Source="{Binding fosaModel.SelectedVideo.fPath}" LoadedBehavior="Manual" />
I can set value of it, when I change videoProgress property, but I can't get the position of video playback, that is, videoProgress isn't updating as video is played. What should I do?
The short answer is that you're going to have to employ a hack no matter what, because MediaElement is not MVVM friendly by default.
Attached properties can be very useful in situations like this though and you can do what you want to do without really breaking the pattern (or at least, not in a way that would upset anyone). You could create an attached DependencyProperty for MediaElement called IsPlaying. In the property change handler, you could either Play or Stop the MediaElement depending on the new value of the property. Something like this might work:
public class MediaElementAttached : DependencyObject
{
#region IsPlaying Property
public static DependencyProperty IsPlayingProperty =
DependencyProperty.RegisterAttached(
"IsPlaying", typeof(bool), typeof(MediaElementAttached ),
new PropertyMetadata(false, OnIsPlayingChanged));
public static bool GetIsPlaying(DependencyObject d)
{
return (bool)d.GetValue(IsPlayingProperty);
}
public static void SetIsPlaying(DependencyObject d, bool value)
{
d.SetValue(IsPlayingProperty, value);
}
private static void OnIsPlayingChanged(
DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
MediaElement me = obj as MediaElement;
if (me == null)
return;
bool isPlaying = (bool)args.NewValue;
if (isPlaying)
me.Play();
else
me.Stop();
}
#endregion
}
Then make an IsPlaying property in your view model, and bind it to the attached property MediaElementAttached.IsPlaying on the MediaElement.
Just keep in mind the binding will only be one-way: you'll be able to control the MediaElement, but you won't be able to use your view model to learn the value of IsPlaying if the user changes the playback state with the transport controls. But you can't really do that with MediaElement anyway, so you're not giving up anything.
As with most things there are multiple ways to skin this cat, but this is my personal preference and keeps you closest to the pattern I think. This way you can at least get that ugly reference to MediaElement out of your view model code.
Also as far as the video not changing with the selection, the way you have it now, the MediaElement's Source property isn't bound to anything because you're creating it in code, so it's not going to change just because SelectedVideo changes. I would go back to the way you had it before, and try my attached property solution.
Edit -
As far as the Position issue goes, it's the same as what I mentioned with IsPlaying, i.e., you can only use these attached properties as one-way setters; you can't use them to obtain the value of anything on the MediaElement. But you have another problem too when it comes to Position which is that it isn't a DependencyProperty, so there's no way to get a notification as to when it actually changes and thus no way to update your attached property with a new value (probably because the updates are so fast and frequent they would bog down the system quite a bit). The only solution I can think of is to poll the MediaElement at some interval (something like 100ms shouldn't be too bad) and then set your attached property with every poll. I would do that in your View's code-behind. Make sure to employ a thread lock and some kind of "suspend update" flag too so you can make sure the MediaElement's Position is not updated during a poll event. Hope this helps.

WPF UserControl exposing properties of subcontrols for binding

I'm working on developing a custom user control for my application. This control is very simple. It's just a grid, with a checkbox in [0,0] and a TextBlock in [0,1]. I've had no issues getting it designed how I'd like in XAML.
However, the second step is giving me some trouble. I'm trying to expose the IsChecked bool? of my sub-control that is a Checkbox for binding on my mainform, and the same idea with the Text property of TextBlock.
I've tried a few different ways of going about this, but to no avail.
Here's the general code I have:
public partial class CDCheckBox : UserControl
{
public bool? IsChecked
{
get { return chk.IsChecked; }
set { chk.IsChecked = value; }
}
public string Text
{
get { return lbl.Text; }
set { lbl.Text = value; }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register(
"IsChecked",
typeof(bool?),
typeof(CDCheckBox),
new PropertyMetadata(default(bool?), OnItemsPropertyChanged));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(CDCheckBox),
new PropertyMetadata(default(string), OnItemsPropertyChanged));
/*
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotify(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
*/
private static void OnItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// AutocompleteTextBox source = d as AutocompleteTextBox;
// Do something...
//lbl.Text = e.NewValue.ToString();
}
/*
public event PropertyChangedEventHandler PropertyChanged;
private void OnNotify(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
*/
public CDCheckBox()
{
InitializeComponent();
}
}
When I run the code above, I get no errors, but my binded data doesn't show up in my TextBlock control. When I tried before I wrote the depenency properties, it gave me an error in my XAML saying "A 'Binding' cannot be set on the 'IsChecked' property of type 'CDCheckBox'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject."
Interestingly however, this error does not appear in the constructor, but instead in the window_loaded method I've written. This appears to be a red herring however, as if I comment out that code, it still fails before the form can display with XAMLParse Error.
Further to my comment, you could try styling an existing control that has the property types that you need. For example, in your custom control you have a nullable Boolean property and a string property. If you repurpose a CheckBox control, it already has a nullable Boolean property (IsChecked) and an object property (Content) which can be used to hold a string.
Here's how you might restyle a CheckBox control and change its template to achieve the result you're after:
<Window x:Class="..."
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<Style x:Key="MySuperCheckboxStyle"
TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{TemplateBinding IsChecked}"
Content="Whatever you need here" />
<TextBlock Grid.Column="1"
Text="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<CheckBox IsChecked="True"
Content="Unstyled check box"
Margin="10" />
<CheckBox Style="{StaticResource MySuperCheckboxStyle}"
IsChecked="True"
Content="Styled check box"
Margin="10" />
</StackPanel>
</Window>
Key here are the TemplateBinding bindings used in the control template. These bind not to a data context like in normal data binding, but rather to properties of the control being templated.
Whenever you find yourself wanting to create a custom control in WPF it is worth exploring whether you can take an existing control and change its appearance to suit what you need, as this is often less work than creating a new control (on the flipside it's not always possible to repurpose an existing control, particularly if you need different behaviour).

Focus on a textbox in autocompletebox?

this is my xaml:
<toolkit:AutoCompleteBox Name="signalNameEditor"
ItemsSource="{Binding MySource}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
IsTextCompletionEnabled="True"
FilterMode="StartsWith"
ValueMemberPath="Label"
MinimumPrefixLength="3"
MinimumPopulateDelay="800"
Style="{StaticResource autoCompleteBoxStyle}">
<toolkit:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="textBlock" Text="{Binding Label}"/>
</StackPanel>
</DataTemplate>
</toolkit:AutoCompleteBox.ItemTemplate>
</toolkit:AutoCompleteBox>
So, how could i get textblock element in my view? I tried this:
var textBlock = signalNameEditor.FindName("textBlock");
but it is wrong. So could you help me with this or redirect me to a proper solution. Thanks in advance.
Thanks for all aswers, that worked
var textBlock = ((StackPanel)signalNameEditor.ItemTemplate.LoadContent()).FindName("textBlock") as TextBlock;
but unfortunately I didn't get the result, that I expected. The question is how to get focus on textbox in autocompletebox, so that when focus is on autocompletebox I could write something there without double clicking.
I thought that I could do something inside my view
public void SetFocus
{
var textBlock = ((StackPanel)signalNameEditor
.ItemTemplate
.LoadContent())
.FindName("textBlock") as TextBlock;
textBlock.Focus();
}
I know that there are a lot of howto examples for setting focus like this one
autocompletebox focus in wpf
but I can't make it work for me. Is there a solution, that I could get without writing AutoCompleteFocusableBox class?
The solution was simplier. Actually i need to set focus on a textbox in a autocompletebox. For this purpose I used style defined as a regular style http://msdn.microsoft.com/ru-ru/library/dd728668(v=vs.95).aspx
After it in my view I could use the following:
public void SetFocus()
{
var textbox = this.editor.Template.FindName("Text", editor) as TextBox;
textbox.Focus();
}
You can Write extension and set custom property for textbox to make it focusable
For example you can write extension class as below
public static class FocusBehavior
{
#region Constants
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached("IsFocused", typeof (bool?),
typeof (FocusBehavior), new FrameworkPropertyMetadata(IsFocusedChanged));
#endregion
#region Public Methods
public static bool GetIsFocused(DependencyObject obj)
{
return (bool) obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
#endregion
#region Event Handlers
private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement) d;
if ((bool) e.NewValue)
uie.Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(() => Keyboard.Focus(uie)));
}
#endregion Event Handlers
}
Then in xaml as below:
<UserControl xmlns:behaviours="clr-namespace:Example.Views.Behaviours">
<TextBox TextWrapping="Wrap" Text="TextBox" behaviours:FocusBehavior.IsFocused={Binding IsFocused}/>
I hope that answeres your question

opening the appbar in metro style apps using binding property

My main page has the appbar and it is shared across different pages. I wrote the following code to open the appbar on the click of a gridview item.
XAML
<AppBar Opened="AppBar_Opened" IsOpen="{Binding IsAppBarOpen}">
Back end
private void Clock_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
App.ViewModel.SelectedClock = (Clock)ThemeGridView.SelectedItem;
App.WorldViewModel.IsAppBarOpen = true;
}
private void ThemeGridView_ItemClick(object sender, ItemClickEventArgs e)
{
App.ViewModel.SelectedClock = (Clock)ThemeGridView.SelectedItem;
App.WorldViewModel.IsAppBarOpen = true;
}
WorldViewModel
private bool _IsAppBarOpen;
public bool IsAppBarOpen
{
get { return _IsAppBarOpen; }
set { base.SetProperty(ref _IsAppBarOpen, value); }
}
GridView XAML
<GridView
Grid.Row="1"
Grid.Column="1"
x:Name="ThemeGridView"
ItemsSource="{Binding Clocks}"
ItemTemplate="{StaticResource WorldClockTemplate}"
SelectionChanged="Clock_SelectionChanged"
SelectionMode="None"
IsItemClickEnabled="True"
ItemClick="ThemeGridView_ItemClick"
>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
But the appbar is not popping up when i select the gridview item. There is no binding error so its really mysterious!
There is not way to bind IsOpen property according the msdn:
Note Binding to the IsOpen property doesn't have the expected results
because the PropertyChanged notification doesn't occur when the
property is set.
<AppBar Opened="AppBar_Opened" IsOpen="{Binding IsAppBarOpen, **Mode=TwoWay**}">
This works for me. I use MVVM Light Toolkit.
public bool AppBarIsOpen
{
get { return this._appBarIsOpen; }
set
{
if (this._appBarIsOpen == value) { return; }
this._appBarIsOpen = value;
this.RaisePropertyChanged("AppBarIsOpen"); // without INotifyPropertyChanged it doesn't work
}
}
<AppBar
IsSticky="True"
IsOpen="{Binding Path=AppBarIsOpen, Mode=TwoWay}">
Roman Weisert's answer correctly states the likely reason for it not working, although you also must make the binding two-way as Zack Weiner suggested (I'm not sure the reason for the latter since the binding is not working in the target-to-source direction anyway). The current value of AppBar.IsOpen may not be reflected by IsAppBarOpen of your view-model. When that's the case, and you try updating the value, it's possible that no PropertyChanged event is raised since you may not actually be updating a value. Instead, you may be just setting the value from false to false or from true to true. Most SetProperty method implementations do not raise the PropertyChanged event unless there is an actual change, and I presume yours is the same.
To fix the problem, consider modifying your view-model as follows:
public bool IsAppBarOpen
{
get { return _IsAppBarOpen; } //changes initiated from UI not reflected
set //not updated from UI
{
_IsAppBarOpen = value;
base.OnPropertyChanged();
}
}
bool _IsAppBarOpen;
The notable difference from your view-model's code, is that SetProperty is not called here so PropertyChanged is raised even when the backing store equals the newly introduced value. In case your base class differs, note that mine has an OnPropertyChanged method with the signature
void OnPropertyChanged( [CallerMemberName] string propertyName = null )
that serves to raise the PropertyChanged event.
I can see from your use of the code-behind, though, that you are not really following MVVM. If MVVM is not a concern to you, then you could forgo the IsAppBarOpen property altogether and just directly set AppBar.IsOpen. As someone who religiously adheres to MVVM, however, I do not recommend that you further head in that (sinful) direction.
I had the same issue and using Caliburn Micro for WinRT and with this code worked for me:
<AppBar IsOpen="{Binding AppBarsOpen}" Name="MainAppBar" Padding="10,0,10,0" AutomationProperties.Name="Bottom App Bar">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="LeftPanel" Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left">
<Button Name="ShowFlyout" Style="{StaticResource BookmarksAppBarButtonStyle}" />
</StackPanel>
<StackPanel x:Name="RightPanel" Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right">
<Button Style="{StaticResource SaveAppBarButtonStyle}" />
</StackPanel>
</Grid>
</AppBar>
And that's your property in ViewModel:
public bool AppBarsOpen
{
get { return _appBarsOpen; }
set
{
if (value.Equals(_appBarsOpen)) return;
_appBarsOpen = value;
NotifyOfPropertyChange(() => AppBarsOpen);
}
}
Had the same issue, solved it by adding the Closed event and updating the ViewModel from the code behind. Saw no other way since TwoWay binding was not working as Roman pointed out.
XAML
<AppBar x:Name="BottomAppBar1"
AutomationProperties.Name="Bottom App Bar"
Closed="BottomAppBar1_Closed"
IsOpen="{Binding IsOpen, Mode=TwoWay}"
IsSticky="True">
C# Code behind
private void BottomAppBar1_Closed(object sender, object e)
{
MainViewModel vm = this.DataContext as MainViewModel;
vm.IsOpen = false;
}
C# MainViewModel
public const string IsOpenPropertyName = "IsOpen";
private bool isOpen = false;
/// <summary>
/// Sets and gets the IsOpen property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public bool IsOpen
{
get
{
return isOpen;
}
set
{
RaisePropertyChanging(IsOpenPropertyName);
isOpen = value;
RaisePropertyChanged(IsOpenPropertyName);
}
}
You should bind both IsOpen and IsSticky two way because otherwise you will get problems with for example having to tap two time to unselect an item (once to close the app bar and once for unselecting) and also it's the will help having your app bar behave more standarly (will prevent the app bar to pop down on tap when an item is selected).
To show the app bar you will need to do the following (the order is important):
this.IsAppBarSticky = true;
this.IsAppBarOpen = true;
and to hide it, do the following:
this.IsAppBarSticky = false;
this.IsAppBarOpen = false;
Another way to make this work without having to use a codebehind handler for app bar closed event:
public class AppBarClosedCommand
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(AppBarClosedCommand), new PropertyMetadata(null, CommandPropertyChanged));
public static void SetCommand(DependencyObject attached, ICommand value)
{
attached.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject attached)
{
return (ICommand)attached.GetValue(CommandProperty);
}
private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Attach click handler
(d as AppBar).Closed += AppBar_onClose;
}
private static void AppBar_onClose(object sender, object e)
{
// Get GridView
var appBar = (sender as AppBar);
// Get command
ICommand command = GetCommand(appBar);
// Execute command
command.Execute(e);
}
}
then in the XAML you can use it like :
common:AppBarClosedCommand.Command="{Binding AppBarClosedCommand}"
with the command function looking like:
public void OnAppBarClosed()
{
AppBarOpen = false;
}

Categories