Is there a way to define XAML elements as non-printable? - c#

I have chunks of XAML displayed on my screen that I make printable with a print button inside that chunk, something like this:
<Border DockPanel.Dock="Top" x:Name="PrintableArea">
<StackPanel
HorizontalAlignment="Right"
VerticalAlignment="Bottom">
<ContentControl Background="Green" x:Name="ManageButtonsContainer"/>
<Button x:Name="PrintButton" Content="Print" Click="Button_Click_Print"/>
</StackPanel>
</Border>
But when this chunk prints out, I don't want the print button to be printed, so I hide it before I print and make it visible again after I print, like this:
private void Button_Click_Print(object sender, RoutedEventArgs e)
{
PrintButton.Visibility = Visibility.Collapsed;
PrintDialog dialog = new PrintDialog();
if (dialog.ShowDialog() == true)
{ dialog.PrintVisual(PrintableArea, "Print job"); }
PrintButton.Visibility = Visibility.Visible;
}
This works, but when the print dialog appears, you see behind the print dialog that the print button disappears and then reappears again, which is just a little unconventional UI behavior that I would like to avoid.
Is there a way to keep elements visible on the screen yet hide them from printing?
e.g. something like this (pseudo-code):
<Button x:Name="PrintButton" Content="Print"
HideWhenPrinting=True"
Click="Button_Click_Print"/>
Pragmatic answer:
Ok, I solved this particular issue simply by changing the visibility only if they actually print, but it would still be nice to know in principle if there is a way to set "printable visibility" in XAML so this issue doesn't always have to be taken care of in code like this:
private void Button_Click_Print(object sender, RoutedEventArgs e)
{
PrintDialog dialog = new PrintDialog();
if (dialog.ShowDialog() == true)
{
PrintButton.Visibility = Visibility.Collapsed;
dialog.PrintVisual(PrintableArea, "Print job");
PrintButton.Visibility = Visibility.Visible;
}
}

I couldn't find any easy answer for your question, so I decided to scary everybody who reads this with the huge code below. It creates attached property, called PrintExtension.IsPrintable, and every time you set it to true on an item, it starts "tracking" that item. Before printing one should call PrintExtension.OnBeforePrinting(), and when you are done call PrintExtension.OnAfterPrinting(). It does exactly the same thing you have in your code, but more effortless.
/// <summary>
/// Hides PrintExtensions.IsPrintable="False" elements before printing,
/// and get them back after. Not a production quality code.
/// </summary>
public static class PrintExtensions
{
private static readonly List<WeakReference> _trackedItems = new List<WeakReference>();
public static bool GetIsPrintable(DependencyObject obj)
{
return (bool)obj.GetValue(IsPrintableProperty);
}
public static void SetIsPrintable(DependencyObject obj, bool value)
{
obj.SetValue(IsPrintableProperty, value);
}
public static readonly DependencyProperty IsPrintableProperty =
DependencyProperty.RegisterAttached("IsPrintable",
typeof(bool),
typeof(PrintExtensions),
new PropertyMetadata(true, OnIsPrintableChanged));
private static void OnIsPrintableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var printable = (bool)e.NewValue;
bool isTracked = IsTracked(d);
if (printable && !isTracked)
{
StartTracking(d);
}
else if (!printable && isTracked)
{
StopTracking(d);
}
}
/// <summary>
/// Call this method before printing.
/// </summary>
public static void OnBeforePrinting()
{
IterateTrackedItems(
item =>
{
var fe = item.Target as FrameworkElement;
if (fe != null)
{
fe.Visibility = Visibility.Collapsed; // Boom, we break bindings here, if there are any.
}
});
}
/// <summary>
/// Call this after printing.
/// </summary>
public static void OnAfterPrinting()
{
IterateTrackedItems(
item =>
{
var fe = item.Target as FrameworkElement;
if (fe != null)
{
fe.Visibility = Visibility.Visible; // Boom, binding is broken again here.
}
});
}
private static void StopTracking(DependencyObject o)
{
// This is O(n) operation.
var reference = _trackedItems.Find(wr => wr.IsAlive && wr.Target == o);
if (reference != null)
{
_trackedItems.Remove(reference);
}
}
private static void StartTracking(DependencyObject o)
{
_trackedItems.Add(new WeakReference(o));
}
private static bool IsTracked(DependencyObject o)
{
// Be careful, this function is of O(n) complexity.
var tracked = false;
IterateTrackedItems(
item =>
{
if (item.Target == o)
{
tracked = true;
}
});
return tracked;
}
/// <summary>
/// Iterates over tracked items collection, and perform eachAction on
/// alive items. Don't want to create iterator, because we do house
/// keeping stuff here. Let it be more prominent.
/// </summary>
private static void IterateTrackedItems(Action<WeakReference> eachAction)
{
var trackedItems = new WeakReference[_trackedItems.Count];
_trackedItems.CopyTo(trackedItems);
foreach (var item in trackedItems)
{
if (!item.IsAlive) // do some house keeping work.
{
_trackedItems.Remove(item); // Don't care about GC'ed objects.
}
else
{
eachAction(item);
}
}
}
}
NB: I haven't tested this code. Be careful with it. As you can see, it's far from being perfect, and I really hope there is simpler solution.
Cheers, Anvaka.

This question is closely related to the one you'd asked an hour earlier (some six years ago, I realize…but I find the answers so far to both unsatisfactory, having recently stumbled across these questions myself, hence these answers). That is, a big part of the problem in both is that you are attempting to use the objects you're displaying on the screen for the purpose of printing, when in fact you should be taking advantage of WPF's data templating features to address your concerns.
In this particular example, you could approach the problem in a few different ways:
Declare a single DataTemplate for on-screen and printing purposes. Including in the view model a flag indicating whether the object is being printed or not. Make a copy of the view model when printing, except set the "is printing" flag to true. In the template, bind the visibility of the Button to this flag (i.e. use a converter to set Visibility="Collapsed" if the flag is true, or define a Trigger that will do the same thing).
Do the above, but instead of including a flag in the view model, when you are printing the data, just explicitly search the visual tree for the Button after the ControlControl has loaded its templated content and collapse the Button before you print the control.
Declare a separate template specifically for the purpose of printing the view model data, and just leave the Button out in that template. This would give you the most control over printing-specific behaviors and appearances, but at the added cost of having to maintain two different-but-related templates.
In all three options, as well as other variations on that theme, the key is that you would use the data templating features to cause WPF to populate a new visual tree to go along with the view model object you're dealing with. In this way, you avoid unwanted interactions between the needs of the printing code and what's happening on the screen (something that is not true for the other answer posted here).
That said, all of these three options have their drawbacks. Copying a view model (per option #1) is fine if it's simple, but it could get unwieldy for more complex data structures. Digging into the generated content (option #2) has obvious negative ramifications, and of course maintaining two different templates (option #3) is just a pain (which in some, but not all cases, could be mitigated by incorporating the "print" template inside the "screen" template via a ContentControl, depending on how important the ordering of the controls is).
Having spent more time dealing with this question in my own code, I've come to the conclusion that, while a bit on the "hacky" side, the solution that works best for me is to set a Trigger that is based on searching for an ancestor element that would be present on the screen, but not when the data template is loaded in a ContentControl. E.g., in the DataTemplate, something like this:
<Button Content="Print" Click="Button_Click_Print">
<Button.Style>
<p:Style TargetType="Button">
<p:Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Window}}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</p:Style.Triggers>
</p:Style>
</Button.Style>
</Button>
I wouldn't say it's 100% kosher for the template to be so self-aware. I prefer they be more agnostic about the context in which they're being used. But you have a fairly unique situation here, one in which being self-aware is going to need to happen one way or the other. I have found this approach to be the least of all possible evils. :)
(Sorry about the p:Style stuff…it's just a XAML-compliant way to work-around the bug in the XML-formatting code Stack Overflow uses, so that code coloring continues to work inside the Style element. You can leave the p: namespace qualifier out in your own XAML if you like, or just go ahead and declare the p: XML namespace appropriately.)

Related

split XAML children into multiple elements in a user control

I've been trying to make a user control for a questionaire application where I have some questions that needs follow-up questions under certain conditions. A simple senario could be that we want to show follow-up quesions if we get a "yes" on some question.
At this point I have followed the example from Zenexer on a similar question. My thought was that I would put the first child of my user control in one container (a StackPanel or whatever) and all
subsequent elements in a second StackPanel. But in the aforementioned example all child elements get stuffed into one element in the user control. To my understanding this is because [ContentProperty(nameof(Children))] is set to all of the content of the user control.
I tried to change the getter and setter of Children in the Zenexer's example but to no avail.
The Question:
Is there a way to spilt the children of my user control into two (or more) elements in my user control with XAML that looks something like this:
MainWindow.xaml
<SubQuestionBox>
<BinaryQuestion
QuestionNumber="4.1"
QuestionText="Parent question"/>
<TextQuestion
QuestionNumber="4.1.1"
QuestionText="Child question 1"/>
<TextQuestion
QuestionNumber="4.1.2"
QuestionText="Child question 2"/>
</SubQuestionBox>
SubQuestionBox.xaml
<UserControl x:Class="SubQuestionBox">
<!--StackPanel To contain the question controls-->
<StackPanel>
<StackPanel x:Name="ParentContainer" />
<StackPanel x:Name="SubQuestionsContainer" />
</StackPanel>
</UserControl>
If anyone wonders how I did it, it goes something like this.
SubQuestionBox.xaml is something like in the original question
SubQuestionBox.xaml.cs
The Children property is a DependencyProperty like in the example of #Zenexer
public SubQuestionBox()
{
InitializeComponent();
Children = SubQuestionsContainer.Children;
// add listener for Loaded event and call OnLoaded()
Loaded += OnLoaded;
if (ParentQuestion != null)
// The BinaryQuestion has a AnswerChanged event
ParentQuestion.AnswerChanged += ToggleCollapse;
}
public void DistributeQuestions()
{
// This method seems super hacky to me.
// I would have thought there is a more elegant way
UIElement parent = null;
if (Children != null)
{
parent = Children[0];
Children.RemoveAt(0);
}
if (ParentContainer != null && parent != null)
{
ParentContainer.Children.Add(parent);
}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
DistributeQuestions();
}

How to handle purely view-related commands?

There is my WPF window in which I placed an ordinary textbox which I would liked to be focused when Ctrl+F is pressed.
As I would like to keep it MVVM-like as much as possible, I use InputBindings on the window to bind that input event to a Command provided in the ViewModel (is that already breaking MVVM pattern because the whole action is only meant to be part of the view? I guess not, as the Command is an object to bind to).
How can the ViewModel communicate with the view to focus the textbox? I read that this already breaks the MVVM pattern, but sometimes simply is necessary as otherwise impossible. However, setting the focus in the ViewModel itself would be totally breaking the MVVM pattern.
I orginally intended to bind the current focused control in the window to a property of the ViewModel but it is quite difficult to even determine the currently focused element in WPF (that always makes me question if it really is the right way to do so).
In cases like this there's just no way to not 'break' pure MVVM. Then again, I'd hardly call it breaking anything. I don't think any decently sized MVVM app out there is 'pure'. So, just stop caring too much about breaking whatever pattern you use and implement a solution instead.
There are at least two ways here:
simply do everything in code behind in the View: check if the key is pressed, if so, set focus. It won't get any simpler than that and you could argue the VM has nothing to do with something that's really all View related
else there is obviously going to have to be some communication between VM and View. And this makes everything more complicated: suppose you use the InputBinding, your command can set a boolean property and then the View can bind to it in turn to set focus. That binding can be done like in Sheridan's answer with an attached property.
Generally, when we want to use any UI event while adhering to the MVVM methodology, we create an Attached Property. As I just answered this very same question yesterday, I would advise you to take a look at the how to set focus to a wpf control using mvvm post here on StackOverflow for a full working code example.
The only difference from that question to yours is that you want to focus the element on a key press... I'm going to assume that you know how to do that part, but if you can't, just let me know and I'll give you an example of that too.
when using mvvm and further when you define a viewmodel with:
a viewmodel should not know/reference the view
then you cant set focus through the viewmodel.
but what i do in mvvm is the following in the viewmodel:
set the focus to the element which is bind to the viewmodel property
for this i create a behavior which simply walk through all control in the visual tree and look for the binding expressions path. and if i find a path expression then simply focus the uielement.
EDIT:
xaml usage
<UserControl>
<i:Interaction.Behaviors>
<Behaviors:OnLoadedSetFocusToBindingBehavior BindingName="MyFirstPropertyIWantToFocus" SetFocusToBindingPath="{Binding Path=FocusToBindingPath, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
</UserControl>
viemodel in any method
this.FocusToBindingPath = "MyPropertyIWantToFocus";
behavior
public class SetFocusToBindingBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty SetFocusToBindingPathProperty =
DependencyProperty.Register("SetFocusToBindingPath", typeof(string), typeof(SetFocusToBindingBehavior ), new FrameworkPropertyMetadata(SetFocusToBindingPathPropertyChanged));
public string SetFocusToBindingPath
{
get { return (string)GetValue(SetFocusToBindingPathProperty); }
set { SetValue(SetFocusToBindingPathProperty, value); }
}
private static void SetFocusToBindingPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as SetFocusToBindingBehavior;
var bindingpath = (e.NewValue as string) ?? string.Empty;
if (behavior == null || string.IsNullOrWhiteSpace(bindingpath))
return;
behavior.SetFocusTo(behavior.AssociatedObject, bindingpath);
//wenn alles vorbei ist dann binding path zurücksetzen auf string.empty,
//ansonsten springt PropertyChangedCallback nicht mehr an wenn wieder zum gleichen Propertyname der Focus gesetzt werden soll
behavior.SetFocusToBindingPath = string.Empty;
}
private void SetFocusTo(DependencyObject obj, string bindingpath)
{
if (string.IsNullOrWhiteSpace(bindingpath))
return;
var ctrl = CheckForBinding(obj, bindingpath);
if (ctrl == null || !(ctrl is IInputElement))
return;
var iie = (IInputElement) ctrl;
ctrl.Dispatcher.BeginInvoke((Action)(() =>
{
if (!iie.Focus())
{
//zb. bei IsEditable=true Comboboxen funzt .Focus() nicht, daher Keyboard.Focus probieren
Keyboard.Focus(iie);
if (!iie.IsKeyboardFocusWithin)
{
Debug.WriteLine("Focus konnte nicht auf Bindingpath: " + bindingpath + " gesetzt werden.");
var tNext = new TraversalRequest(FocusNavigationDirection.Next);
var uie = iie as UIElement;
if (uie != null)
{
uie.MoveFocus(tNext);
}
}
}
}), DispatcherPriority.Background);
}
public string BindingName { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectLoaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObjectLoaded;
}
private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
{
SetFocusTo(AssociatedObject, this.BindingName);
}
private DependencyObject CheckForBinding(DependencyObject obj, string bindingpath)
{
var properties = TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.All) });
if (obj is IInputElement && ((IInputElement) obj).Focusable)
{
foreach (PropertyDescriptor property in properties)
{
var prop = DependencyPropertyDescriptor.FromProperty(property);
if (prop == null) continue;
var ex = BindingOperations.GetBindingExpression(obj, prop.DependencyProperty);
if (ex == null) continue;
if (ex.ParentBinding.Path.Path == bindingpath)
return obj;
}
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var result = CheckForBinding(VisualTreeHelper.GetChild(obj, i),bindingpath);
if (result != null)
return result;
}
return null;
}
}
(is that already breaking MVVM pattern because the whole action is
only meant to be part of the view? I guess not, as the Command is an
object to bind to)
The Command system in WPF was actually not designed around data-binding, but the UI -- using RoutedCommands, a single command would have different implementations based on the physical position in the UI structure of the element that called the command.
Commanding Overview
Your flow would be:
Ctrl+F is pressed
command event is raised and bubbles up
the event reaches the window, which has a CommandBinding to the command
event handler on the window focuses the text box
If the current element is inside a container that wants to handle the command differently, it will stop there before it reaches the window.
This is probably closer to what you want. It may make sense to involve the view model if there is some concept of an "active property" like in blindmeis's answer, but otherwise I think you would just end up with a redundant / circular flow of information e.g. key pressed -> view informs viewmodel of keypress -> viewmodel responds by informing view of keypress.
After a few days of getting a better grip on all of this, considering and evaluating all options, I finally found a way to work it out. I add a command binding in my window markup:
<Window.InputBindings>
<KeyBinding Command="{Binding Focus}" CommandParameter="{Binding ElementName=SearchBox}" Gesture="CTRL+F" />
</Window.InputBindings>
The command in my ViewModel (I cut the class down to what matters in this case):
class Overview : Base
{
public Command.FocusUIElement Focus
{
get;
private set;
}
public Overview( )
{
this.Focus = new Command.FocusUIElement();
}
}
And finally the command itself:
class FocusUIElement : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute ( object parameter )
{
return true;
}
public void Execute ( object parameter )
{
System.Windows.UIElement UIElement = ( System.Windows.UIElement ) parameter;
UIElement.Focus();
}
}
This might not be straigt MVVM - but stijn's answer has a good point:
So, just stop caring too much about breaking whatever pattern you use
and implement a solution instead.
Normally I take care of keeping stuff organised by conventions, especially when I am still new to something, but I do not see anything wrong regarding this.

Copying a TabItem with an MVVM structure

This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.
Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.
Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.
Here is a sample of a command that I've implemented:
public viewModel()
{
allowReversing = new Command(allowReversing_Operations);
}
public Command AllowReversing
{
get { return allowReversing; }
}
private Command allowReversing;
private void allowReversing_Operations()
{
//Query for Window1
var mainWindow = Application.Current.Windows
.Cast<Window1>()
.FirstOrDefault(window => window is Window1) as Window1;
if (mainWindow.checkBox1.IsChecked == true) //Checked
{
mainWindow.checkBox9.IsEnabled = true;
mainWindow.groupBox7.IsEnabled = true;
}
else //UnChecked
{
mainWindow.checkBox9.IsEnabled = false;
mainWindow.checkBox9.IsChecked = false;
mainWindow.groupBox7.IsEnabled = false;
}
}
*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.
Now to the question:
After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads:
I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)
Thank you for your help.
Revision of Leo's answer
This is the current version of Leo's answer that I have compiling. (There were some syntax errors)
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
Here is my example of a properly-implemented dynamic TabControl in WPF.
The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.
The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.
This removes the need for "cloning" the UI or do any other weird manipulations with it.
1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.
2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.
I have this code that I made
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] {new PropertyFilterAttribute(PropertyFilterOptions.SetValues)})
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd where dpd != null select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly))
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
So it would be like
var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);

What is the best way to know if the user has changed data in the DataGrid?

I would like to know every time a user modifies data in WPF DataGrid.
Is there a single event that I can use to do that? Or what is the minimal set of events that I can use to cover full set of data changes (Add row, delete row, modify row etc)?
I know that this is probably more than you are asking for, but once you do it, it's hard to go back. Whatever you are binding to ... some List, have that item implement IEditableObject.
that way you won't have to ever worry about whatever control/view implementation, events ets.
When the item is changed, the datagrid as well as plethora of .NET controls will set the IsDirty object to true.
These are not super great links but they will get you started thinking about maintaining isDirty flag.
https://msdn.microsoft.com/en-us/library/system.componentmodel.ieditableobject(v=vs.110).aspx
object editing and isDirty() flag
http://bltoolkit.net/doc/EditableObjects/EditableObject.htm
this is more what I am used to:
https://stackoverflow.com/a/805695/452941
Usually, when you are using MVVM, you bind the master list to an ObservableCollection and then the selected item to a specific instance. Inside your setters, you can raise events. This would be the most logical (read: the most common method I've seen) to capture updates / adds / deletes to a list of data.
MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="MainWindow" Height="350" Width="525">
<DataGrid x:Name="dataGrid" AutoGeneratingColumn="OnAutoGeneratingColumn">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<EventSetter Event="Binding.SourceUpdated" Handler="OnDataGridCellSourceUpdated"/>
<EventSetter Event="Binding.TargetUpdated" Handler="OnDataGridCellTargetUpdated"/>
</Style>
</DataGrid.Resources>
</DataGrid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.dataGrid.ItemsSource = new ObservableCollection<Person>()
{
new Person() { Name = "John", Surname = "Doe" },
new Person() { Name = "Jane", Surname = "Doe" }
};
}
private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dataGridBoundColumn = e.Column as DataGridBoundColumn;
if (dataGridBoundColumn != null)
{
var binding = dataGridBoundColumn.Binding as Binding;
if (binding != null)
{
binding.NotifyOnSourceUpdated = true;
binding.NotifyOnTargetUpdated = true;
}
}
}
private void OnDataGridCellSourceUpdated(object sender, DataTransferEventArgs e)
{
this.OnDataGridCellChanged((DataGridCell)sender);
}
private void OnDataGridCellTargetUpdated(object sender, DataTransferEventArgs e)
{
this.OnDataGridCellChanged((DataGridCell)sender);
}
private void OnDataGridCellChanged(DataGridCell dataGridCell)
{
// DataContext is MS.Internal.NamedObject for NewItemPlaceholder row.
var person = dataGridCell.DataContext as Person;
if (person != null)
{
var propertyName = ((Binding)((DataGridBoundColumn)dataGridCell.Column).Binding).Path.Path;
var propertyValue = TypeDescriptor.GetProperties(person)[propertyName].GetValue(person);
// TODO: do some logic here.
}
}
}
This is what I used for some complex DataGridCell formatting based on a Person (just some POCO) instance, property name and property value.
But if you want to be able to know when to save the data and you use MVVM, then the best way to do this would be to have original value and current value for every editable property in your view model / model. When data is loaded, original and current value would be equal, and if property is changed through DataGrid or any other way, only current value is updated. When data needs to be saved, just check if any item has any property that has different original and current value. If the answer is yes, then data should be saved, because it has been changed since last load / save, otherwise data is same as when loaded, so no new saving is required. Also, when saving, current values must be copied to original values, because data is again equal to the saved data, like when it was last loaded.
if you use mvvm you do not need to know when the "user modify data in the data grid" you have to know when the underlying collection change.
so if you use datatable(HasChanges/RejectChanges...) you have that all built in. if you use poco collections then your items at least have to implement INotifyPropertyChanged - if its raised the user modify data. Maybe IEditable is a good one too for reject changes and so on.

Scroll a WPF FlowDocumentScrollViewer from code?

I have a FlowDocumentScrollViewer I want to automatically scroll to the bottom
when text is added.
<FlowDocumentScrollViewer Name="Scroller">
<FlowDocument Foreground="White" Name="docDebug" FontFamily="Terminal">
<Paragraph Name="paragraphDebug"/>
</FlowDocument>
</FlowDocumentScrollViewer>
In code I add Inlines to the Paragraph, but when there is to much text I would
like to be able to simply scroll down using code instead of having the user doing so.
Any suggestions?
try:
Scroller.ScrollViewer.ScrollToEnd();
Where "Scroller" is the name of your FlowDocumentScrollViewer.
EDIT: I wrote this answer a little too quickly. FlowDocumentScrollViewer does not expose a ScrollViewer property. I had actually extended the FlowDocumentScrollViewer class and implemented the ScrollViewer property myself. Here is the implementation:
/// <summary>
/// Backing store for the <see cref="ScrollViewer"/> property.
/// </summary>
private ScrollViewer scrollViewer;
/// <summary>
/// Gets the scroll viewer contained within the FlowDocumentScrollViewer control
/// </summary>
public ScrollViewer ScrollViewer
{
get
{
if (this.scrollViewer == null)
{
DependencyObject obj = this;
do
{
if (VisualTreeHelper.GetChildrenCount(obj) > 0)
obj = VisualTreeHelper.GetChild(obj as Visual, 0);
else
return null;
}
while (!(obj is ScrollViewer));
this.scrollViewer = obj as ScrollViewer;
}
return this.scrollViewer;
}
}
I've faced a similar problem: I wanted a textual area which could hold my text, is able to wrap it, it fills its parent control and is scrollable.
First I've tried to use a TextBlock with a ScrollViewer and I think it worked, but for some reason I've wanted to use a FlowDocument instead with a FlowDocumentScrollViewer. This latter didn't work and I just couldn't leave the fight unattented so I tried to find solutions and this is how I got here. I've tried to apply the workarounds presented in the answers to the original question, however neither solutions worked out for me (I'm using .NET 4.5, maybe it works in other versions, but I don't know about that).
I've tried using a single FlowDocument by itself also, but the control contains some UI elements I didn't want. So, I came up with another solution.
<ScrollViewer VerticalScrollBarVisibility="Auto">
<FlowDocumentScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<FlowDocument>
That's right. It works! Calling ScrollViewer.ScrollToBottom() just works! The ScrollViewer enables scrolling and FlowDocumentScrollViewer removes the UI elements from the FlowDocument. Hope it helps!
Apparently my construction had a flaw, because this way the FlowDocument isn't scrollable via a mouse's scrolling wheel. However setting the FlowDocumentScrollViewer control's IsHitTestVisible property to False solves this.
The other answers given here are a bit puzzling, since I don't see any public "ScrollViewer" property on the FlowDocumentScrollViewer.
I hacked around the problem like this. Beware that this method can return null during initialization:
public static ScrollViewer FindScrollViewer(this FlowDocumentScrollViewer flowDocumentScrollViewer)
{
if (VisualTreeHelper.GetChildrenCount(flowDocumentScrollViewer) == 0)
{
return null;
}
// Border is the first child of first child of a ScrolldocumentViewer
DependencyObject firstChild = VisualTreeHelper.GetChild(flowDocumentScrollViewer, 0);
if (firstChild == null)
{
return null;
}
Decorator border = VisualTreeHelper.GetChild(firstChild, 0) as Decorator;
if (border == null)
{
return null;
}
return border.Child as ScrollViewer;
}
This question was asked 7 years ago, now I have the same problem, and I find a simple solution. The follow code add a Section to Flowdocument which same to Paragraph, then scroll to the end.
private void addSection(Section section)
{
section.Loaded += section_Loaded;
fdoc.Blocks.Add(section);
}
private void section_Loaded(object sender, RoutedEventArgs e)//scroll to end
{
var sec = sender as Section;
if (sec != null)
{
sec.BringIntoView();
}
}
This may be a very late answer, but I've found a way to do this.
//after your FlowDocumentScrollViewer(for example, x:Name="fdsv") loaded
ScrollViewer sv = fdsv.Template.FindName("PART_ContentHost", fdsv) as ScrollViewer;
sv.ScrollToBottom();
sv.ScrollToTop();
sv.ScrollToVerticalOffset(100);
// etc.
Check IScrollInfo and ScrollViewer for details.
I hope this helps you.

Categories