Access Datacontext from binded user control - c#

I have build a dynamic UserControl from an ObservableCollection as follows...
public static ObservableCollection<Model.Model.ControleData> ListControleMachine = new ObservableCollection<Model.Model.ControleData>();
public Genkai(string Autorisation) {
InitializeComponent();
DataContext = this;
icTodoList.ItemsSource = ListControleMachine;
Model.Model.ControleData v = new Model.Model.ControleData();
v.ComputerName = "M57095";
v.ImportSource = "LOAD";
ListControleMachine.Add(v);
}
XAML
<ItemsControl x:Name="icTodoList" ItemsSource="{Binding ListControleMachine}" >
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:ControlMachineII}">
<local:ControlMachineII />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But how can I access the DataContext from C# code?
For example say I want to delete the UserControl with a close button in itself, I need at least access ControleData.ComputerName value then remove it from Mainform.ListControleMachine.
I can't find the best practice for achieve this and play with my data in UserControl code.
The remove button code is like this i think (with hard coded value)
Genkai.ListControleMachine.Remove(Genkai.ListControleMachine.Where(X => X.ComputerName == "M57095").Single());

i finaly found that my DataContext was not yet initialized at start that why i got error so i had to wait for the datacontext first: here code for correction
public ControlMachineII()
{
InitializeComponent();
DataContextChanged += new DependencyPropertyChangedEventHandler(ControlMachineII_DataContextChanged);
}
private void ControlMachineII_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
string compname = (this.DataContext as Model.Model.ControleData).ComputerName;
Console.WriteLine("DataContext initialized computername :" +compname);
}

I saw you posted same question today with some more data. I'm going to present the solution using that data.
Solution 1 :
Use Tag property of button like below:
<Button Content="Close this UC" HorizontalAlignment="Left" Margin="414,22,0,0"
VerticalAlignment="Top" Width="119" Click="Button_Click" Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
Event handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
List<object> list = (button.Tag as ItemsControl).ItemsSource.OfType<TodoItem>().ToList<object>();
list.Remove(button.DataContext);
(button.Tag as ItemsControl).ItemsSource = list;
}
Solution 2:
More elegant solution:
Create this Style in your MainWindow:
<Window.Resources>
<Style TargetType="Button">
<EventSetter Event="Click" Handler="Button_Click"/>
</Style>
</Window.Resources>
So now the Handler of any Button Click event in any MainWindow's descendant Button Control is in the MainWindow.xaml.cs.
Then place the handler method in MainWindow.xaml.cs and change the handler like below:
private void Button_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
items.Remove(button.DataContext as TodoItem);
icTodoList.ItemsSource = null;
icTodoList.ItemsSource = items;
}

Related

WPF - Remove a "User Control" Child from a StackPanel

I'm trying to make a WPF UI where the user can edit a query to search the database. The query is created according to what the consumer chooses from the comboboxes Like This and he can create as much filters as he wants as long as he clicks the Add new Condition button.
I created the comboboxes template as a User Control like this :
User control XAML:
<StackPanel Orientation="Horizontal" >
<Button
Name="DeleteFilter"
HorizontalAlignment="Left"
Margin="5"
Content="-"
Click="DeleteFilter_OnClick">
</Button>
<ComboBox
Text="Property"
x:Name="Property"
Width="100"
DataContext="{StaticResource SomeViewModel}"
ItemsSource="{Binding Properties}"
DisplayMemberPath="Name"
SelectionChanged="Property_OnSelectionChanged"/>
<ComboBox
Text="PropertyOperator"
x:Name="Operator"
ItemsSource="{Binding Operators}"
DisplayMemberPath="Name"
SelectionChanged="Operator_OnSelectionChanged">
</ComboBox>
<TextBox
x:Name="Value"
Text="Value"
TextAlignment="Center"
Width="100"
Margin="5"/>
</StackPanel>
Whenever the user clicks the Add new Condition button, I call this event:
private void AddFilterButton_OnClick(object sender, RoutedEventArgs e)
{
var conditionUserControl = new ConditionUserControl();
StackPanel.Children.Add(conditionUserControl);
}
Everything works correctly.
My Question:
How can I delete the User Control child from clicking the DeleteFilter button that exists in the User Control template.
I tried this:
StackPanel.Children.Remove(..);
to remove the child from my MainWindow but how to know which child the user clicked.
Try this:
private void DeleteFilter_OnClick(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
var conditionUserControl = FindParent<ConditionUserControl>(btn);
if (conditionUserControl != null)
{
var sp = FindParent<StackPanel>(conditionUserControl);
if (sp != null)
sp.Children.Remove(conditionUserControl);
}
}
private static T FindParent<T>(DependencyObject dependencyObject) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null) return null;
var parentT = parent as T;
return parentT ?? FindParent<T>(parent);
}
Another answer to #mm8 answer is :
Update the AddFilterButton_OnClick:
I did this and the functionality works:
private void AddAndFilterButton_OnClick(object sender, RoutedEventArgs e)
{
var conditionUserControl = new ConditionUserControl();
StackPanel.Children.Add(conditionUserControl);
conditionUserControl.DeleteFilter.Click += (o, args) => StackPanel.Children.Remove(conditionUserControl);
}

How do I handle a selected item of LongListMultiSelector?

I have list of items in LongListMultiSelector - how to handle a selected item?
My LongListMultiSelector xaml:
<tkit:LongListMultiSelector Name="longlist" SelectionChanged="longlist_SelectionChanged">
<tkit:LongListMultiSelector.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" FontSize="32" Tap="TextBlock_Tap"/>
</DataTemplate>
</tkit:LongListMultiSelector.ItemTemplate>
</tkit:LongListMultiSelector>
TextBlock tap event handler code:
private void TextBlock_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
var itemTapped = (sender as FrameworkElement).DataContext as Book;
}
LongListMultiSelector SelectionChanged event handler code:
private void longlist_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
I found part of solution here, however, The problem if at least one item is selected, then textblockTap event doesn't handle - longlist_SelectionChanged event handles everything. How can i fix that?
Once you are using LongListMultiSelector, the SelectionChanged event is fired when item is added or removed. If you want to perform the action regardless item is added/removed, I've managed to do it like this (for a simle string):
private void longlist_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string selectedItem = String.Empty;
if (e.AddedItems.Count > 0) selectedItem = e.AddedItems[0] as string;
else selectedItem = e.RemovedItems[0] as string;
MessageBox.Show(selectedItem); // do your work
}
It should run while items are selected separately by tapping, but this method will have problems when more items are added/removed at the same time - if you need it, then you should handle this also.
Your XAML DataTemplate.
<DataTemplate x:Key="listItemTemplate">
<StackPanel Orientation="Horizontal" Margin="4,4">
<TextBlock Tap="textblockTap" Margin="0,-7,0,0" Text="{Binding Name}" Style="{StaticResource PhoneTextLargeStyle}"/>
</StackPanel>
</DataTemplate>
In your CS page;
private void textblockTap(object sender, EventArgs e)
{
var file = (TextBlock)sender;
var ContentFile = (string)file.Text;
MessageBox.Show(ContentFile);
}
This example will show you the text of the selected item in the MessageBox.

Give focus to a component inside an expander

I have this requirement where I need to focus the first element inside the expander when the user press tab.
Currently (default behavior) the focus goes to the expander, I've tried to focus the first element of the expander by creating a focus event handler in the expander like this:
private void ExpanderGotFocus(object sender, RoutedEventArgs e)
{
var expander = (Expander) sender;
if (!expander.IsExpanded)
{
expander.IsExpanded = true;
this._someText.Focus();
}
}
Which doesn't work.
I've also tried to give the focus the the next element:
var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
var keyboardFocus = Keyboard.FocusedElement as UIElement;
keyboardFocus.MoveFocus(tRequest);
But only works the second time ( when the expander has been at least opened once )
I've tried to put this in a thread and some other crazy ideas.
How can I give focus to the first element inside an expander? ( the first time the expander is closed )
I tried several ways and none of them worked, basically the problem is the TextBox is still rendering when the expander is expanding ( to early ).
So instead what I've found is to add the IsVisibleChanged event to the textbox so when the expander finished the textbox become visible and request the focus
XAML
<Expander GotFocus="ExpanderGotFocus">
<Expander.Header>
<TextBlock Text="{x:Static Client:Strings.XYZ}" />
</Expander.Header>
<Expander.Content>
<StackPanel>
<TextBox IsVisibleChanged="ControlIsVisibleChanged" Name="txtBox" />
</StackPanel>
</Expander.Content>
</Expander>
Code behind
private void ExpanderGotFocus(object sender, RoutedEventArgs e)
{
var expander = (Expander) sender;
if (!expander.IsExpanded )
{
expander.IsExpanded = true;
}
}
private void ControlIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Keyboard.Focus((IInputElement)sender);
}
Check with the following,
XAML code:
<StackPanel>
<Expander Header="Expander"
Name="expander"
Collapsed="OnCollapsed"
IsExpanded="True" >
<StackPanel>
<TextBox Text="Text1" Name="textBox1" />
<TextBox Text="Text2" Name="textBox2" />
<TextBox Text="Text3" Name="textBox3" />
</StackPanel>
</Expander>
<TextBox Text="Text4" Name="textBox4" />
</StackPanel>
in the code behind:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.Loaded += delegate
{
textBox2.Focus();
};
}
private void OnCollapsed(object sender, RoutedEventArgs e)
{
var element = Keyboard.FocusedElement;
if (element != null)
{
//now is the ToggleButton inside the Expander get keyboard focus
MessageBox.Show(element.GetType().ToString());
}
//move focus
Keyboard.Focus(textBox4);
}
}

I have a context menu which is being shared by 6 different labels, how can I tell which label is using the current instance of the context menu?

Here is the xaml for the contextMenu:
<Window.Resources>
<ContextMenu x:Key="IBContextMenu" x:Shared="true" Name="IBContextMenu1">
<MenuItem Header="Edit" Click="ibEdit_Click" AllowDrop="False" />
<MenuItem Header="Clear" Click="ibClear_Click"/>
</ContextMenu>
</Window.Resources>
Both the edit and clear items' methods need to know which label to act upon. How can I do this?
I think you are looking for PlacementTarget:
http://msdn.microsoft.com/en-us/library/system.windows.controls.contextmenu.placementtarget.aspx
If you switch over to a Command-pattern, you can actually get this via Binding and pass it along as the CommandParameter...
Here's an answer I came up with. I don't really like it because it's a bit hack-ish, but it works. The idea is that you make your labels listen to the MouseRightButtonUp event, which is fired when the user releases the right mouse button after clicking to open the context menu. In the event handler, you set a private Label variable to the label that the user just right-clicked. Then, in your MenuItem click handler, you can access that private Label variable. Note that all the labels you want to do this must use the same event handler for MouseRightButtonUp.
For example:
<Window.Resources>
<ContextMenu x:Key="MyMenu">
<MenuItem Header="Edit" Click="Edit_Click"/>
<MenuItem Header="Clear" Click="Clear_Click"/>
</ContextMenu>
</Window.Resources>
<StackPanel>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some text</Label>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some junk</Label>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some stuff</Label>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some 0000</Label>
</StackPanel>
Code behind:
private void Edit_Click(object sender, RoutedEventArgs e)
{
if (clickedLabel != null)
{
MessageBox.Show(clickedLabel.Content.ToString());
}
}
private void Clear_Click(object sender, RoutedEventArgs e)
{
if (clickedLabel != null)
{
MessageBox.Show(clickedLabel.Content.ToString());
}
}
private Label clickedLabel;
private void Label_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
clickedLabel = (Label)sender;
}
Try to set a DataContext to the Labels, for example
And in the Click event just check the ((FrameworkElement)sender).DataContext for FIRST/SECOND etc. Let us know if that works.

How to reference right-clicked object in WPF Context Menu item click event handler?

In WPF application there is a Grid with a number of objects (they are derived from a custom control). I want to perform some actions on each of them using context menu:
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Name="EditStatusCm" Header="Change status" Click="EditStatusCm_Click"/>
</ContextMenu>
</Grid.ContextMenu>
But in the event handler I cannot get know which of the objects was right-clicked:
private void EditStatusCm_Click(object sender, RoutedEventArgs e)
{
MyCustControl SCurrent = new MyCustControl();
MenuItem menu = sender as MenuItem;
SCurrent = menu.DataContext as MyCustControl; // here I get a run-time error
SCurrent.Status = MyCustControl.Status.Sixth;
}
On that commented line Debugger says: Object reference not set to an instance of an object.
Please help, what is wrong in my code?
Edited (added):
I tried to do the same, using Command approach:
I declared a DataCommands Class with RoutedUICommand Requery and then used Window.CommandBindings
<Window.CommandBindings>
<CommandBinding Command="MyNamespace:DataCommands.Requery" Executed="RequeryCommand_Executed"></CommandBinding>
</Window.CommandBindings>
XAML of MenuItem now looks like:
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Name="EditStatusCm" Header="Change status" Command="MyNamespace:DataCommands.Requery"/>
</ContextMenu>
</Grid.ContextMenu>
And event handler looks like:
private void RequeryCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
IInputElement parent = (IInputElement)LogicalTreeHelper.GetParent((DependencyObject)sender);
MyCustControl SCurrent = new MyCustControl();
SCurrent = (MuCustControl)parent;
string str = SCurrent.Name.ToString();// here I get the same error
MessageBox.Show(str);
}
But debugger shows the same run-time error: Object reference not set to an instance of an object.
What is missing in my both approaches?
How I should reference right-clicked object in WPF Context Menu item click event handler?
note the CommandParameter
<Grid Background="Red" Height="100" Width="100">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem
Header="Change status"
Click="EditStatusCm_Click"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
and use it in the handler to figure out which Grid it is
private void EditStatusCm_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null)
{
ContextMenu cm = mi.CommandParameter as ContextMenu;
if (cm != null)
{
Grid g = cm.PlacementTarget as Grid;
if (g != null)
{
Console.WriteLine(g.Background); // Will print red
}
}
}
}
Update:
If you want the menuitem handler to get to the Grid's children instead of the Grid itself, use this approach
<Grid Background="Red" Height="100" Width="100">
<Grid.Resources>
<ContextMenu x:Key="TextBlockContextMenu">
<MenuItem
Header="Change status"
Click="EditStatusCm_Click"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Parent}" />
</ContextMenu>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="ContextMenu" Value="{StaticResource TextBlockContextMenu}" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="Row0" Grid.Row="0" />
<TextBlock Text="Row1" Grid.Row="1" />
</Grid>
Just replace the TextBlocks with whatever your custom object type is. Then in the event handler, replace Grid g = cm.PlacementTarget as Grid with TextBlock t = cm.PlacementTarget as TextBlock (or whatever your custom object type is).
By binding the Data Context like so in the xaml:
ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource= {RelativeSource Self}}">
You can then do this:
private void Context_MenuClick(object sender, RoutedEventArgs e)
{
var menuItem = e.Source as MenuItem;
MyDoStuffFunction(menuItem.DataContext);
}
The data context will be bound to the object that was clicked to open the ContextMenu.
I got it from a codeproject article at this link:
http://www.codeproject.com/Articles/162784/WPF-ContextMenu-Strikes-Again-DataContext-Not-Upda
menu = sender as MenuItem will be null if the sender is not a MenuItem or a derived class thereof. Subsequently trying to dereference menu will blow up.
It's likely that your sender is a Menu or ContextMenu or a ToolStripMenuItem or some other form of menu item, rather than specifically being a MenuItem object. Use a debugger breakpoint to stop the code here and examine the sender object to see exactly what class it is.
For RoutedEventArgs
RoutedEventArgs.source is the reference to the object that raised the event
RoutedEventArgs.originalSource is the reporting source as determined by pure hit testing, before any possible Source adjustment by a parent class.
So .Sender should be the answer. But this depends on how the menuitems are added and bound
See this answer collection and choose the method that will work for you situation!
Shouldn't you be checking RoutedEventArgs.Source instead of sender?
You had two different problems. Both problems resulted in the same exception, but were otherwise unrelated:
First problem
In your first approach your code was correct and ran well except for the problem here:
SCurrent.Status = MyCustControl.Status.Sixth;
The name "Status" is used both as a static member and as an instance member. I think you cut-and-pasted the code incorrectly into your question.
It may also be necessary to add the following after MenuItem menu = sender as MenuItem;, depending on your exact situation:
if(menu==null) return;
Second problem
In your second approach you used "sender" instead of "e.Source". The following code works as desired:
private void RequeryCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
IInputElement parent = (IInputElement)LogicalTreeHelper.GetParent((DependencyObject)e.Source);
// Changed "sender" to "e.Source" in the line above
MyCustControl SCurrent = new MyCustControl();
SCurrent = (MuCustControl)parent;
string str = SCurrent.Name.ToString();// Error gone
MessageBox.Show(str);
}
Final Note
Note: There is no reason at all to bind CommandParameter for this if you use the commanding approach. It is significantly slower and takes more code. e.Source will always be the source object so there is no need to use CommandParameter, so use that instead.
This works for me:-
XAML:-
<DataGrid.ContextMenu>
<ContextMenu x:Name="AddColumnsContextMenu" MenuItem.Click="AddColumnsContextMenu_Click">
</ContextMenu>
For adding menu items:-
foreach (String s in columnNames)
{
var item = new MenuItem { IsCheckable = true, IsChecked = true ,Header=s};
AddColumnsContextMenu.Items.Add(item);
}
And here comes the listener:-
private void AddColumnsContextMenu_Click(object sender, RoutedEventArgs e)
{
MenuItem mi = e.Source as MenuItem;
string title = mi.Header.ToString();
MessageBox.Show("Selected"+title);
}
Thanks...
In my case I was able to use:
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem menuItem = e.Source as MenuItem;
ContextMenu parent = menuItem.Parent as ContextMenu;
ListBoxItem selectedItem = parent.PlacementTarget as ListBoxItem;
}

Categories