WPF: Locate Parent Window from ListView ViewBase - c#

I have a ListView that allows the user to change the ViewBase through a Context Menu (it acts like a simplified version of windows explorer).
<ListView Name="lv" Grid.Row ="0" Grid.Column ="1" >
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="View1" Click="SwitchViewMenu"/>
<MenuItem Header="View2" Click="SwitchViewMenu"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<local:View1 />
</ListView.View>
</ListView>
The ViewBases DataTemplates are defined in a Generic.XAML file, and i use the following Function to change the chosen view :
void ChangeView(string str)
{
if (str == "View1")
{
lv.View = lv.FindResource("View1") as ViewBase;
}
else if (str == "View2")
{
lv.View = lv.FindResource("View2") as ViewBase;
}
}
The problem:
I got a custom CheckBox control in all the DataTemplates, that has a predefined click event attached, however when i try to move up the Parents of the CheckBox, the highest level i can reach is the Parenting Grid in the DataTemplate in use.
What i need to access is the Parent Window itself.
Note:
I tried to add a Dependency Property to the Custom CheckBox Control and bind it to an extra defined variable in the sent object (The data template's items DataType object) that had a window reference as its value, but i kept getting null even though all the other dependency properties/values got bound.

It depends on how you're trying to get to the parents. I highly recommend this article (Josh Smith) to get a real understanding of what's going on in WPF.
That said, you could try Window.GetWindow(myControl); (static method); it should work for any pure WPF trees (for interop with WinForms, see this).

Related

Windows Universal App (XAML): ListviewItem on selected advanced view (Show button only when ListViewItemsis selected)

I'm working on my first Universal App (I'm beginner with it).
In app I have few places, where I want to select ListViewItem, and remove/edit (http://i.imgur.com/ZRkIQHm.png) it by some button. I'm not a designer, so I want to choose some simple, good-looking solution how to display these buttons.
I was inspired by Windows 10, there are few places, where you click on ListViewItem, and this selected item shows some buttons (Printer setting, WiFi connection, Bluetooth connection) - like on this image - http://i.imgur.com/WjerA5F.png.
I've spend lot of time with googling, trying, and now I have no idea how to do it. I was closest probably with using VisualStateManager in ListViewItemContainer.
Here is code example (in c# is filled ItemsSource by some ObservableCollection<>):
<ListView Name="dbList" Grid.Column="0" SelectionChanged="dbList_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="XXX">
<TextBlock Text="{Binding}"/>
<Button Name="removeDatabaseButton" Click="removeDatabaseButton_Click" Content="Remove" Tag="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Thanks!
PS: Sorry for my english :)
The answer is quite complicated:
You need to modify your list Type Class which should implement the INotifyPropertyChanged interface which has the property Visible.
private Visibility _visible;
public Visibility Visible //Binding to this in Listbox - Button Visibility
{
get { return _visible; }
set { _visible = value; } //Call OnPropertyChanged method
}
Now create a listview_itemclick() method. Call the listview.selectedindex and set the visibility of the listitem property "Visible" to Visible - the event PropertyChanged will fire and the ui gets notified that the visibility of this specific button should change.
Helping links:
INotifyPropertyChangedInterface
Playlist to MVVM tutorials - containing INotifyPropertyChanged

navigation/load different views on WPF/MVVM

I am quite new to WPF development, and currently I am trying to use the MVVM on my application development. I have read a lot about MVVM navigation and switching views, but I can't find a solution for my current situation. Let's explain what it is:
First of all, I have my main View element, a Dockpanel, with some fixed areas, and a main "dynamic" area where the content should change, depending on actions:
<DockPanel>
<Label Content="Top Fixed element"/>
<StackPanel Orientation="Vertical" Height="auto" Width="150" DockPanel.Dock="Left">
<Label Content="SomeOptions"/>
<!-- some more elements -->
</StackPanel>
<Label DockPanel.Dock="Bottom" Content="Foot"/>
<ContentControl Content="{Binding CurrentMainViewElementViewModel}"/>
</DockPanel>
I have defined some DataTemplates that I would like to load in this ContentControl, here there is one of the Data Templates as example:
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:FileLoaderVM}">
<View:FileLoaderView/>
</DataTemplate>
</Window.Resources>
This FileLoader (View and View Model are implemented, using the RelayCommand and the INotifyPropertyChanged) opens a dialog box after clicking a button, where after selecting a file it is opened and parsed, and show all the found elements inside a ListView with multiple selection(in this case, persons with their data).
What I want to do now is to load another user control in this ContentControl, when I click a button. This button is defined in my view model like this:
public ICommand LoadPersons
{
get { return new RelayCommand(param => this.loadSelectedPersons(), param => (SelectedPersons!=null && SelectedPersons.Any()));}
}
My question comes at this point, how can I modify the content of the ContentControl, loading another User Control instead of the current one directly from my view model (in this "this.loadSelectedPersons()")?
If this is not possible, how should I approach to solve this problem?
Next to this action, I want to show all the previously selected elements and manipulate in different possible ways (inserting in a DB, saving in another file and so on), and I have already for that the appropriate User Control, that I would like to show in my main view element in the ContentControl section, keeping the other elements as they are originally.
lets see if i get you right.
you have a mainviewmodel with a property (CurrentMainViewElementViewModel) bound to the ContentControl. your MainViewmodel set the FileLoaderVM to this Property. now you wanna show a "new/other" Viewmodel when a File is seleted in your FileLoaderVM?
why dont you simply expose a event from your FileLoaderVM and subscribe to this event in your MainViewModel? if you do so your MainViewModel can then set the "new/other" Viewmodel to the ContentControl
To change content of ContentControl you do not load another user control, but change value of CurrentMainViewElementViewModel (to which ContentControl.Content is bound) to a new ViewModel, which will load another UserControl (defined in DataTemplate same way as FileLoaderVM is).
This looks like a job for main ViewModel (where CurrentMainViewElementViewModel is located).
Easiest solution is to provide a method in that ViewModel
public Switch()
{
CurrentMainViewElementViewModel = SomeViewModel;
}
and call this method from FileLoaderVM.

WPF and MVVM: bind UserControl to XAML dynamically

seems like a trivial task: i am building a wpf application, using MVVM pattern. what i want is dynamically change part of a view, using different UserControls, dependent on user input.
let's say, i have got 2 UserControls, one with a button, and another with a label.
in main view i have a container for that. following XAML "works":
<GroupBox Header="container" >
<local:UserControlButton />
</GroupBox>
and a UserControl element with buttons pops up. if i change it to another one, it works too.
question is how to feed that groupbox dynamically. if i put something like that in my model view:
private UserControl _myControl;
public UserControl MyControl
{
get
{
return _myControl;
}
set
{
_myControl= value;
InvokePropertyChanged("MyControl");
}
}
and change my view XAML to something like:
<GroupBox Header="container" >
<ItemsControl ItemsSource="{Binding MyControl}" />
</GroupBox>
and feed it from command with usercontrol for button or for label: nothing happens, although "MyControl" variable is set and is "invoke property changed"..
Obviously there are many ways to skin this particular cat - but to answer the question of why it doesn't work you need to look into the ItemsSource property of ItemsControl on MSDN.
The items control is designed to show multiple items, provided through an IEnumerable passed to the ItemsSource property. You are passing a UserControl, so the binding will fail.
For your example, I would change the ItemsControl to a ContentControl and bind the content to your MyControl property. This should then work.
<GroupBox Header="container" >
<ContentControl Content="{Binding MyControl}" />
</GroupBox>
However, I would strongly recommend looking into other ways of doing this - having a control in your VM breaks MVVM to my mind. Depending on what you are doing look at data templates - #Sheridan's link in the comments provides an great description of a way to do it.
Couldn't post this as a comment so adding as answer..
Have a look at this:
Implementing an own "Factory" for reusing Views in WPF
It uses DataTemplates but doesn't require the DataTemplate section for each view. If you potentially have a lot of user controls/views you wish to display or you are reusing through multiple views or you are intending to actually dynamically generate a view (versus just loading an existing user control) then this might suite your needs.

Where is WPF setting GridViewRowPresenter.Columns for GridView

I'm recently exploring a sample of using the GridViewRowPresenter inside a TreeView to make it like a "treegrid". To do this one generally need to define a GridViewColumnCollection, and set it to GridViewRowPresenter.Columns explicitly. The sample works for me very well.
However since GridViewRowPresenter is most often used with ListView and GridView. I begin to wonder how does the ListView and GridView setting the Columns property for each row. The xaml most likely like this:
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Length" DisplayMemberBinding="{Binding}"/>
</GridView>
</ListView.View>
<ListViewItem>Hello</ListViewItem>
<ListViewItem>World</ListViewItem>
</ListView>
Note that though the GridViewColumnCollection is defined explicitly, it's not set anywhere for the ListViewItem. Eventually each ListViewItem will contain a GridViewRowPresenter, but in no where the Columns property is set. I checked the open source code for ListView.cs, ListView.xaml, ListViewItem.cs, ListViewItem.xaml, GridViewRowPresenter.cs GridViewRowPresenterBase.cs, in none of these the Columns property is set.
The closest thing I found is GridView.ColumnCollection property, I suspect it played some role in connecting the GridViewRowPresenter.Columns and GridView.Columns, but how?
GridView.ColumnCollection is the thing you seem to be looking for.
GridViewRowPresenter holds the same instance of ColumnCollection and once arranging its cells it runs through the list of columns for additional information such as desired width or desired DisplayMemberBinding.
There is also a GridViewHeaderPresenter and it does the same as RowPresenter just instead of creating cells per line it creates headers.
Edit:
You can see once you reflector the code following statement.
protected internal override void PrepareItem(ListViewItem item)
{
base.PrepareItem(item);
// attach GridViewColumnCollection to ListViewItem.
SetColumnCollection(item, _columns);
}
That is how GridViewColumnCollection is being passed/attached to ListViewItem.
Its internal as you can see.
I will show how to probe into the GridViewColumn and GridViewRowPresenter interactions at runtime. Consider the following simplified XAML:
<ListView x:Class="demo_listview"
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wd="clr-namespace:System.Windows.Documents;assembly=PresentationFramework"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<!-- ... -->
<ListView.View>
<GridView>
<!-- ... -->
<GridViewColumn x:Name="my_gv_column" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="my_textblock" Text="ABCD" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- ... -->
</GridView>
</ListView.View>
</ListView>
Now in code, override a method such as ListView.ClearContainerForItemOverride as shown below. The code will modify the background color of the TextBlock to make it red:
Note that the red color persists after "Clearing" due to containers being recycled. I picked that function to keep the demo simple, since it ensures that the templates will be fully instantiated for examination. Otherwise, for example, if you get 'null' for gv_row, for example, you need to call lvi.ApplyTemplate() immediately prior.
I assume you are able to get the ListViewItem easily enough for the row you're interested in. So from any fully-instantiated ListViewItem at runtime, we can now see how to get to the contents of a particular instantiated CellTemplate at runtime. Additional remarks follow below the code.
public partial class demo_listview : ListView
{
protected override void ClearContainerForItemOverride(DependencyObject el, object item)
{
base.ClearContainerForItemOverride(el, item);
var lvi = (ListViewItem)el;
var gv_row = (GridViewRowPresenter)lvi.Template.FindName("5_T", lvi);
var ix = ((GridView)View).Columns.IndexOf(my_gv_column);
var cp = (ContentPresenter)VisualTreeHelper.GetChild(gv_row, ix);
var tb = (TextBlock)my_gv_column.CellTemplate.FindName("my_textblock", cp);
tb.Background = Brushes.Red;
}
};
The first step is to get the GridViewRowPresenter from the ListViewItem. There are a few ways to do this. Above, I use the magic constant "5_T" to get the instantiation from the template name. This constant is as defined by WPF, so it will work as long as you are using the standard ListViewItem its built-in control template.
Next we need the index of the column we're interested in. The column instance itself is easily accessed in C# by giving it a name (my_gv_column) in the XAML. As for its index, I'm not sure if the IndexOf technique shown here will continue to work if columns are reordered. If not, the correct result would be obtained by fetching, via reflection, the value of the private _actualIndex field (or ActualIndex property) from the GridViewColumn of interest.
Next, using VisualTreeHelper.GetChild, get the visual child of the GridViewRowPresenter which corresponds to the desired column index; this will be the ContentPresenter for the cell! With this, you can do another FindName search within the template to find the actual instantiated control you're looking for, by name. In the example, we successfully get the TextBlock instance and then change its background color to demonstrate the result.
It's done in FrameworkElementFactory.ApplyAutoAliasRules:
// GridViewRowPresenter auto-aliases Content and Columns to Content
// property GridView.ColumnCollection property on the templated parent.
https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/FrameworkElementFactory.cs,9ce27c5244be816c,references
So, GridViewRowPresenter.Columns defaults to {TemplateBinding GridView.ColumnCollection}.

Silverlight 4: How to find source UI element from contextmenu's menuitem_click?

I have a datagrid and I added silverlight 4 toolkit contextmenu to textbox in datagrid as follows. When users right click on the textbox, contextmenu is being displayed. When users click the menu item with Header "Test", "MenuItem_Click" is getting executed. Now I want to access the textbox from the MenuItem_Click and modify its properties like background etc. Is there anyway to find textbox element(which is contextmenu's parent) from MenuItem_Click event?
It appears to me that I am missing something very simple.
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding AcctId}"
Style="{StaticResource documentTextBoxStyle}"
ToolTipService.ToolTip="Right Click to modify parameters" >
<toolkit:ContextMenuService.ContextMenu >
<toolkit:ContextMenu >
<toolkit:MenuItem Header="Test" Click="MenuItem_Click"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</TextBox>
</DataTemplate>
There's really no need for a workaround, it's as simple as using the databinding:
(sender as MenuItem).DataContext as TextBox
Will give you the TextBox you're after. (Storing stuff in the Tag field is really not something you want to clutter your code with.)
Though I did not find a solution to this, I found couple of workarounds
Traverse the visual tree and findout the textbox
Modify the code in control toolkit sources to expose the internal member 'Owner' as a public Property which contains reference to the owner of the context menu, in my case, the textbox.
I wonder why SL toolkit guys made the owner to be internal not public. Probably their idea is to manage 'ContextMenu' only through 'ContextMenuService' but unfortunately ContextMenuService doesnt give the Owner. Hopefully SL toolkit guys will give us a way to get the owner of the context menu in future releases.
I'm not sure if this works in Silverlight, but I had a similar issue with WPF recently. If you use the ContextMenu's PlacementTarget property, it should return the element that was used to open the ContextMenu.
All I can suggest is giving your MenuItem a Tag with it's parent's TextBlock name like this:
EDIT: Can't figure out how to paste in Xaml, but I'm sure you know how to add this.
Then in your click event you find the TextBlock:
private void MenuItem_TextBlockClick(object sender, RoutedEventArgs e)
{
MenuItem menuItem = (MenuItem)sender;
TextBlock textBlock = this.FindName((string)menuItem.Tag) as TextBlock;
/// do something
}
The issue I found was the parent of the MenuItem is ContextMenu, which is fine. But once you try and get the Parent of the ContextMenu it just crashes.

Categories