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.
Related
I have an application in which I set the content of a contentpresenter, dependent on the datatype by a datatemplate (see MainWindow). The Datatemplate is a usercontrol, which is actually datatype specific. (The small example below is only for demonstration, but in my "real" application the user shall be able to switch between different data.)
The usercontrol (UserControl1) has a DependencyProperty which I assign a value (in my application this is actually a binding to a VM, just set it to a string in example for simplicity).
Setting the value is still working fine. However In my UserControl I need to react to changes of the DependencyProperty to change the view of my UserControl (or later on CustomControl). So I implemented a OnPropertyChangend method.
When application starts OnPropertyChanged works as I expect it and I get the "correct" newvalue of my DependencyProperty. However, if I change my VM (i.e. my datatemplate changes) during runtime by clicking on a button, OnPropertyChanged returns the DependencyProperty's defaultvalue.
In my small example application, I can see that the value is set correctly, as the Textblock content changes to the correct value.
It only seems that OnPropertyChanged gets fired before my DependencyProperty's value gets the new value. So, it's not possible for me to react on the new value.
It is not really clear why this happens. Seems to have something to do with the order in which WPF resolves internal stuff?
Does anyone have a clue, how I can fix this behavior and get access to the current/last value when changing my VM and don't miss an update? As stated out before, I need to react on that value.
Maybe I am doing something totally stupid here. Is the approach I decided to use here a bad one? Are DataTemplates the wrong approach to switch between two pairs? What would be a better approach then? However, I guess it won't be possible to avoid the DependencyProperty and the UserControl in my application.
MainWindow.xaml
<!--MainWindow.xaml -->
<Grid>
<StackPanel>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ContentPresenter Content="{Binding ActiveVM}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<local:UserControl1 MyProperty="Test1"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<local:UserControl1 MyProperty="Test2"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
vmParent = new VMParent();
DataContext = vmParent;
var vm1 = new VM1();
var vm2 = new VM2();
}
VMParent vmParent;
private void Button_Click(object sender, RoutedEventArgs e)
{
vmParent.ChangeActiveVM();
}
}
UserControl1.xaml
<!--UserControl1.xaml -->
<TextBlock Text="{Binding MyProperty, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}}}"/>
UserControl1.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string), typeof(UserControl1), new PropertyMetadata("DefaultString", OnMyPropertyChangend));
private static void OnMyPropertyChangend(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == "DefaultString")
{
;
//xxxxxx
//unexpectedly i get stuck here
//Would expect/need NewValue to be Text1/Text2 to react to it
//xxxxxx
}
}
}
VMParent
class VMParent : INotifyPropertyChanged
{
public VMParent()
{
vm1 = new VM1();
vm2 = new VM2();
ActiveVM = vm1;
}
public event PropertyChangedEventHandler PropertyChanged;
VM1 vm1;
VM2 vm2;
public object ActiveVM
{
get => m_activeVM;
set { m_activeVM = value; OnPropertyChanged("ActiveVM"); }
}
private object m_activeVM;
protected internal void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
public void ChangeActiveVM()
{
if (ActiveVM is VM1)
ActiveVM = vm2;
else
ActiveVM = vm1;
}
}
VMs are only used to apply Datatemplate
class VM1
{
}
class VM2
{
}
I am quiet new to programming and am currently learning C# and the MVVM pattern.
I need to code a database tool for ChiliPlants for university.
There you should be able to add a new object to an ObservableCollection.
To add a new Item to this ObservableCollection a new Window opens. It looks like this:
Window Add
I now want the two RadioBoxes to be bound to a property called "HybridSeed". Which is defined in the ViewModel:
//Public Property HybridSeed
public bool HybridSeed
{
get { return ChiliModel.HybridSeed; }
set
{
if (ChiliModel.HybridSeed == value)
return;
ChiliModel.HybridSeed = value;
OnPropertyChanged("HybridSeed");
}
}
The RadioBox part of my View looks like this:
<RadioButton Grid.Row="5" Content="Ja" Grid.Column="1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<RadioButton Grid.Row="5" Content="Nein" Grid.Column="1" HorizontalAlignment="Left" Margin="89,10,0,0" VerticalAlignment="Top"/>
But how to bind the outcome of a user clicking on these RadioButtons to this HybridSeed Property? Important is that the outcome is a bool.
I looked up almost every entry similar to this topic, but I did not find a simple solution. Or a solution which I was able to understand with my bad coding skills :( ...
I would be very happy if you guys could help me. Please keep it simple for this newbie :)
If there is a simpler solution using a CheckBox or a ComboBox it would also be perfect. The most important thing is to have a nice user interface. Right now it only works with a TextBox where the user always has to write "True" or "False".
Solution:
I added the IsClicked Property in the "Yes" RadioButton to be bound to my boulean property with: IsClicked="{Binding HybridSeed}". Thanks to naslund for his fast answer :)
Just bind HybridSeed to the Yes-radiobutton. It will then either be true if the user has selected that or false if No-radiobutton has been selected (or if nothing has been selected). Binding to both buttons in this case is a bit redundant since the mechanism of radiobuttons takes care of it.
WPF:
<RadioButton Content="Yes" IsChecked="{Binding HybridSeed}" />
<RadioButton Content="No" />
<Label Content="{Binding HybridSeed}" ContentStringFormat="Value is: {0}" />
Logic:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
private bool hybridSeed;
public bool HybridSeed
{
get { return hybridSeed; }
set
{
hybridSeed = value;
OnPropertyChanged(nameof(HybridSeed));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
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;
}
I am binding a Collection at run time to a Combobox and I would like to set the Index after to 0. I could not find a straight answer to what I want.
_stationNames = new ObservableCollection<string>(_floorUnits.Unit.Select(f => f.Name));
_stationNames.Insert(0, "All");
stationsComboBox.ItemsSource = _stationNames;
stationsComboBox.SelectedIndex = 0;//Doesn;t work
Xaml
<ComboBox x:Name="stationsComboBox" Grid.Row="1" Grid.Column="1" Text="{Binding Name}"
SelectionChanged="StationComboBoxSelectionChanged" VerticalAlignment="Center" Margin="3"
SelectedIndex="0"/>
It sounds like you're trying to use it like you would with WinForms. WPF is a slightly different beast and a lot more powerful regarding bindings.
I recommend reading a bit on MVVM to get the most benefit from WPF. By binding the XAML to a view model class (rather than trying to wire things up in Code-behind) you will find you can accomplish what you want with a lot more flexibility without oodles of code.
For instance: Given the following VM:
public class MyViewModel: INotifyPropertyChanged
{
public ObservableCollection<string> StationNames
{
get;
private set;
}
public Something()
{
StationNames = new ObservableCollection<string>( new [] {_floorUnits.Unit.Select(f=>f.Name)});
StationNames.Insert(0, "All");
}
private string _selectedStationName = null;
public string SelectedStationName
{
get
{
return _selectedStationName;
}
set
{
_selectedStationName = value;
FirePropertyChanged("SelectedStationName");
}
}
private void FirePropertyChanged(string propertyName)
{
if ( PropertyChanged != null )
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You can set your view's (XAML form) DataContext to an instance of the ViewModel and update your combo box definition to:
<ComboBox x:Name="stationsComboBox" Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Path=StationNames}" SelectedItem={Binding Path=SelectedStationName} VerticalAlignment="Center" Margin="3"
SelectedIndex="0"/>
From here whenever the combo box selection changes, the VM's SelectedStationName updates to reflect the current selection, and from anywhere in the VM code, setting the VM's SelectedStationName will update the combo's selection. (I.e. implementing a Reset button, etc.)
Normally though, with something like what you've suggested, I would be looking at binding directly to the Units collection. (or VM's derived from units if they themselves can be viewed/edited.) In any case it should give you a bit of a starting point to start researching into WPF bindings.
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" />