split XAML children into multiple elements in a user control - c#

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();
}

Related

FindVisualChild cannot find Grid in DataTemplate

I am trying to use a basic implementation of "FindVisualChild" for WPF in order to find a specific Grid that exists within a DataTemplate of a ListBox.
The implementation is as follows:
private DependencyObject FindVisualChild<T>(DependencyObject obj, string name)
{
Console.WriteLine(((FrameworkElement)obj).Name);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
FrameworkElement fe = child as FrameworkElement;
//not a framework element or is null
if (fe == null) return null;
if(!string.IsNullOrEmpty(fe.Name))
Console.WriteLine(fe.Name);
if (child is T && fe.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase))
return child;
else
{
//Not found it - search children
DependencyObject nextLevel = FindVisualChild<T>(child, name);
if (nextLevel != null)
return nextLevel;
}
}
return null;
}
My issue is that this code was working yesterday to find a Grid that I have defined in the DataTemplate with the name "MainTermServListGrid" as shown here:
<ListBox HorizontalAlignment="Stretch" Grid.Row="1" x:Name="TermServListBox" ItemsSource="{Binding TermServs}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="MainTermServListGrid">
//code here
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
However, today when I try to use the same method to find that Grid, the result is always null. If I debug and step through the code, it looks like it is not even finding any of the items that exist within the DataTemplate.
I am calling the FindVisualChild method right after I populate the ListBox with items. Could it be that I am not waiting long enough and the window does not have enough time to finish initializing and presenting the new items in the list box before I am trying to find a specific child within that listbox?
If that is the case, would a simple call to await Task.Delay(500) work to give the UI enough time to finish loading? Or am I doing something totally wrong here?
Turns out I was right in assuming I was not giving the UI enough time to finish loading. I believe it is due to the fact that I was populating the ListBox with items using a particular method, and at the end of that method I was raising an Event which triggered the search in the Window's code-behind.
Because there was never a period if time between finishing the loading of the items and the event being raised to look for them, I dont think the ui had time to finish initializing everything.
Basically all I did to fix the issue in my event handler was the following:
private async void ViewModelOnListPopulated(object sender, EventArgs eventArgs)
{
await Task.Delay(500);
//Continue on to find the visual child...
}

How to get UIElement out of templated data in ListView?

Okay, i feel slightly dumb for askin this but, I have a listview with a templated class MyClass or whatever, whenever i "myListView.Add(new MyClass())" the winrt platform adds a new UIElement there and binds the proper properties into their proper uielements properly, now, I want to be able to iterate through these logical items (myListView.Items or myListView.SelectedItems) and get their corresponding UIElement for animation, is that possible?
like for example
class PhoneBookEntry {
public String Name { get;set }
public String Phone { get;set }
public PhoneBookEntry(String name, String phone) {
Name = name; Phone = phone;
}
};
myListView.Add(new PhoneBookEntry("Schwarzeneger", "123412341234");
myListView.Add(new PhoneBookEntry("Stallone", "432143214321");
myListView.Add(new PhoneBookEntry("Statham", "567856785678");
myListView.Add(new PhoneBookEntry("Norris", "666666666666");
And in XAML (just an example so i can explain what I mean)
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Phone}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
So, my point and objective here is to
foreach(PhoneBookEntry pbe in myListView.Items) // or SelectedItems
{
UIElement el; // How can I get the UIElement associated to this PhoneBookEntry pbe?
if(el.Projection == null)
el.Projection = new PlaneProjection;
PlaneProjection pp = el.Projection as PlaneProjection;
// Animation code goes here.
if(myListView.SelectedItems.Contains(pbe)
//something for selected
else
//something for not selected
}
I just need a way to get an UIElement which is being used to represent this logical data class PhoneBookEntry in the templated listview.
Also, this necessity comes with a very big problem I'm having where, selected items doesn't differ visually on Windows Phone -_- any ideas?
You can also use the ListView.ContainerFromItem or ListView.ContainerFromIndex methods which will return the container UI element for a given item in the list view (of course, only if the container is generated)
Ok I may look like a fool answering my own question but i've figured a way out.
First things first: ListViews only create UIElements for determinate items in the list (the ones cached and the ones being shown). So if you do add 2000 items to myListView.Items, the effective ammount of UIElements representing these items will be 56 or close number.
Because, the ItemListView simulates the UIElements even if they're not there, just to give size and position to the scrollbar (hence why scrolling down on very large lists cause some lag, WinRT is unloading UIElements and loading new ones)
From that, I figured out I could simply iterate through the current list of loaded UIElements through
// For each of the cached elements
foreach(LIstViewItem lvi in myListView.ItemsPanelRoot.Children)
{
// Inside here I can get the base object used to fill the data template using:
PhoneBookEntry pbe = lvi.Content as PhoneBookEntry;
if(pbe.Name == "Norris")
BeAfraid();
// Or check if this ListViewItem is or not selected:
bool isLviSelected = lvi.IsSelected;
// Or, like I wanted to, get an UIElement to animate projection
UIElement el = lvi as UIElement;
if(el.Projection == null)
el.Projection = new PlaneProjection();
PlaneProjection pp = el.Projection as PlaneProjection;
// Now I can use pp to rotate, move and whatever with this UIElement.
}
So, this is it. Right beneath my nose...

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.

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

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.)

How can I make a specific TabItem gain focus on a TabControl without click event?

How can I tell my TabControl to set the focus to its first TabItem, something like this:
PSEUDO-CODE:
((TabItem)(MainTabControl.Children[0])).SetFocus();
How about this?
MainTabControl.SelectedIndex = 0;
this.tabControl1.SelectedTab = this.tabControl1.TabPages["tSummary"];
I've found it's usually a best practice to name your tabs and access it via the name so that if/when other people (or you) add to or subtact tabs as part of updating, you don't have to go through your code and find and fix all those "hard coded" indexes. hope this helps.
I realise this was answered a long time ago, however a better solution would be to bind your items to a collection in your model and expose a property that selected item is bound to.
XAML:
<!-- MyTemplateForItem represents your template -->
<TabControl ItemsSource="{Binding MyCollectionOfItems}"
SelectedItem="{Binding SelectedItem}"
ContentTemplate="{StaticResource MyTemplateForItem}">
</TabControl>
Code Behind:
public ObservableCollection<MyItem> MyCollectionOfItems {
get;
private set;
}
private MyItem selectedItem;
public MyItem SelectedItem{
get { return selectedItem; }
set {
if (!Object.Equals(selectedItem, value)) {
selectedItem = value;
// Ensure you implement System.ComponentModel.INotifyPropertyChanged
OnNotifyPropertyChanged("SelectedItem");
}
}
}
Now, all you have to do to set the item is:
MyItem = someItemToSelect;
You can use the same logic with the SelectedIndex property, further, you can use the two at the same time.
This approach allows you to separate your model correctly from the UI, which could allow you to replace the TabControl with something else down the line but not requiring you to change your underlying model.
Look at the properties for the tab control...
Expand the TabPages properties "collection"...
Make note of the names you gave the members.
ie. a tab control called tabMain with 2 tabs called tabHeader and tabDetail
Then to select either tab...You have to set it with the tabname
tabMain.SelectedTab = tabHeader;
tabControl1.SelectedTab = item;
item.Focus();
Basically all of the answers here deal with SELECTION, which does not answer the question.
Maybe that is what OP wanted, but the question very specifically asks for FOCUS.
TabItem item = (TabItem)MainTabControl.Items[0];
// OR
TabItem item = (TabItem)MainTabControl.SelectedItem;
// Then
item.Focus();
tabControl.SelectedItem = tabControl.Items[0];
If you have a Tabcontroller named tabControl you could set the selectedIndex from different methods, i use following methods mostly.
codebehind:
tabControl.SelectedIndex = 0; // Sets the focus to first tabpanel
clientside:
First, put the following javascript in your aspx/ascx file:
<script type="text/javascript">
function SetActiveTab(tabControl, activeTabIndex) {
var activeTab = tabControl.GetTab(activeTabIndex);
if(activeTab != null)
tabControl.SetActiveTab(activeTab);
}</script>
Then add following clientside event to prefered controller:
OnClientClick="function(s, e) { SetActiveTab(tabControl, 0);
it's better to use the following type of code to select the particular
item in the particular tab...
.
private void PutFocusOnControl(Control element)
{
if (element != null)
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
(System.Threading.ThreadStart)delegate
{
element.Focus();
});
}
And in calling time... tabcontrol.isselected=true;
PutFocusOnControl(textbox1);
will works fine...
Private Sub TabControl1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles TabControl1.SelectedIndexChanged
'MsgBox(TabControl1.SelectedIndex)
If TabControl1.SelectedIndex = 0 Then
txt_apclntFrstName.Select()
Else
txtApplcnNo.Select()
End If
End Sub
It worked for me to set focus to the last tab just after I open it:
//this is my assignment of the collection to the tab control
DictTabControl.DataContext = appTabs.DictTabs;
//set the selected item to the last in the collection, i.e., the one I just added to the end.
DictTabControl.SelectedItem = DictTabControl.Items[(DictTabControl.Items.Count-1)];

Categories