I need to temporarily show some UIElement in FlowDocument. I encapsulate it in BlockUIContainer but I don't see how it can be disconnected from BlockUICOntainer when I don't need it anymore. There's no Remove method. Below code shows that - it ends with exception
System.InvalidOperationException: 'Specified element is already the logical child of another element. Disconnect it first.'
E.g.
<Grid x:Name="Grid1">
<RichTextBox x:Name="rtb"/>
</Grid>
public MainWindow() {
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e) {
var c2 = new WrapPanel();
// Imagine adding lots of controls into c2 ...
rtb.Document.Blocks.Add(new BlockUIContainer(c2));
var bc = (BlockUIContainer)c2.Parent;
((FlowDocument)(bc).Parent).Blocks.Remove(bc);
Grid1.Children.Add(c2); // Exception. Here I want to move that c2 elsewhere in logical tree but I don't know how to disconnect it
}
Sure, I could recreate c2 but that's not nice. Or I see I could call internal RemoveLogicalChild but that also seems hacky. How WPF expects this is done?
Thank you
You have to properly disconnect the element and its child elements from the visual tree. The XAML rules don't allow multiple references to UIElemnts in the tree. References are not tracked, which means there are no aliases of a single instance. Each UIElement element must be an individual instance.
You can decide to create new instances or properly clear the association:
var c2 = new WrapPanel();
rtb.Document.Blocks.Add(new BlockUIContainer(c2));
var bc = (BlockUIContainer) c2.Parent;
rtb.Document.Blocks.Remove(bc);
// Remove parent association
bc.Child = null;
// Update the subtree/template association
c2.UpdateLayout();
Grid1.Children.Add(c2);
Related
Im trying to add a UserControl in WPF to a grid, but it doesnt show up when im trying to add via MyGrid.Children.Add(UserControl). So i tried to display the number of childs of my grid and it says 1 after adding the usercontrol. (MyGrid.Children.Clear() doesn't work too. After clearing the grid it says that there are 0 childs left but there are still some UiElements when im compiling my program.)
This problem appears only in 1 function. In an other function (the same class) i can easily add childs to the same grid (myGrid).
My code:
private void AddDateOnClick(object sender, MouseButtonEventArgs e)
{
MyGrid.Children.Clear();
UserControlAddDate ucad = new UserControlAddDate();
MyGrid.Children.Add(ucad);
MessageBox.Show(MyGrid.Children.Count.ToString()); //Only to test if there are some childs
}
When i try to clear this grid in a other function (same class) it clears the grid. Only clearing in this function is a problem. Im not understanding why???
What is this UserControlAddDate? Maybe it is not initialized. That’s why it’s not getting added to the grid..
In that place try to add a textbox to the grid and check if it’s working. If it’s working then it’s the problem with your code.
As mentioned by Ed Plunkett, please try to use templates and databinding. It's the best way to work with WPF.
Try this anyway.
private void AddDateOnClick(object sender, MouseButtonEventArgs e)
{
MyGrid.Children.Clear();
TextBox ucad = new TextBox();
ucad.Text = “TEST”;
MyGrid.Children.Add(ucad);
MessageBox.Show(MyGrid.Children.Count.ToString()); //Only to test if there are some childs
}
I've read a few articles on SO:
How to detrmine the control that cause ContextMenuStrip
Getting the control of a context menu
and a couple others that suggested use of the SourceControl property.. but none work in this context:
I have a ContextMenuStrip that has a child ToolStripMenuItem - this code from the windows forms designer generated section:
// _tileContextMenuStrip
//
this._tileContextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.tileKindToolStripMenuItem,
this.forceWidthScalingToolStripMenuItem,
this.forceHeightScalingToolStripMenuItem});
this._tileContextMenuStrip.Name = "_tileContextMenuStrip";
this._tileContextMenuStrip.Size = new System.Drawing.Size(184, 70);
//
// tileKindToolStripMenuItem
//
this.tileKindToolStripMenuItem.Name = "tileKindToolStripMenuItem";
this.tileKindToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
this.tileKindToolStripMenuItem.Text = "Tile Kind";
So the context menu strip and the menu item first in the list are fixed at design time. At runtime, the TSMI has child TSMIs added to it in a loop based on an enum:
foreach(TileKind t in typeof(TileKind).GetEnumValues()) {
ToolStripMenuItem tsmi = new ToolStripMenuItem(t.ToString("g"));
tsmi.Tag = t;
tsmi.Click += tsmi_Click;
tileKindToolStripMenuItem.DropDownItems.Add(tsmi);
}
Later I have 20 checkboxes on my form and I set their .ContextMenuStrip to be the same thing:
foreach(Thing t in someDataSource){
CheckBox c = new CheckBox();
c.Text = t.SomeData;
c.ContextMenuStrip = this._tileContextMenuStrip;
myPanelBlah.Controls.Add(c);
}
Great, so now I have all my checkboxes and they all show the context menu when I right click them, but when I choose one the sub-menu items, I just can't find out the control that fired the context menu...
//this the click handler for all the menu items dynamically added
void tsmi_Click(object sender, EventArgs e)
{
ToolStripMenuItem tsmi = sender as ToolStripMenuItem;
(tsmi.OwnerItem //the parent node in the menu tree hierarchy
.Owner as ContextMenuStrip) //it's a ContextMenuStrip so the cast succeeds
.SourceControl //it's always null :(
}
I can reliably get ahold of the contextmenustrip either by routing up from the event handler sender, or even just by referencing the ContextMenuStrip itself as a form instance variable, but SourceControl is always null
Any ideas what to try next?
I see the problem, quacks loudly like a bug. There's a workaround, you can subscribe the ContextMenuStrip's Opening event. At that point, well before you start navigating into the sub-items, the SourceControl property is still valid. So store it in a field of the class so you'll have it available in the Click event handler. Roughly:
private Control _tileCmsSource;
private void _tileContextMenuStrip_Opening(object sender, CancelEventArgs e) {
_tileCmsSource = _tileContextMenuStrip.SourceControl;
}
void tsmi_Click(object sender, EventArgs e)
{
ToolStripMenuItem tsmi = sender as ToolStripMenuItem;
// Use _tileCmsSource here
//...
}
I want to draw lines in a "touch" application in WPF and wrote the following code:
XAML (part):
<Canvas x:Name="MainCanvas"
IsManipulationEnabled="True"
TouchDown="MainCanvas_TouchDown"
TouchUp="MainCanvas_TouchUp">
C#:
public partial class MainWindow : Window
{
Line myLine = new Line();
public MainWindow()
{
InitializeComponent();
}
public void MainCanvas_TouchDown(object sender, TouchEventArgs e)
{
myLine.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
myLine.StrokeThickness = 2;
// Line's starting point
myLine.X1 = e.GetTouchPoint(this).Position.X;
myLine.Y1 = e.GetTouchPoint(this).Position.Y;
}
public void MainCanvas_TouchUp(object sender, TouchEventArgs e)
{
// Line's ending point
myLine.X2 = e.GetTouchPoint(this).Position.X;
myLine.Y2 = e.GetTouchPoint(this).Position.Y;
MainCanvas.Children.Add(myLine);
}
}
With this, I'm able to draw only one line. If I try to draw another, the application crashes.
For each line that you want to draw, you will have to create a new Line instance. In your current code, you have just one Line instance, but that instance represents exactly one displayed line.
Unlike other graphics toolkits where you use various objects to draw graphical elements onto a drawing surface step by step, WPF works like vector graphics: Each of the objects you use is a graphical element; for two identical graphical elements, you will need two objects with the same properties.
Therefore, when you add the same graphical object for the second time, in this line:
MainCanvas.Children.Add(myLine);
An exception will be thrown, because you can only add each graphical element once.
As you want to add an arbitrary number of lines, store your lines in a list and add a new line instance (i.e. instantiate the Line class and add your new line to the canvas) in the touch-down event. In the touch-up event, set the second point of the new instance (the last element in your list).
Please be aware that this is not about C#, it is about WPF. (In other words, a C# pro who has never worked with WPF will not be able to tell you, but a WPF developer who has never used C# (always VB.NET, for example) might help.
Thanks to O. R. Mapper's answer, here's the solution. As he explains, for each line a new Line instance must be created.
public partial class MainWindow : Window
{
List<Line> lines = new List<Line>();
public void MainCanvas_TouchDown(object sender, TouchEventArgs e)
{
Line myLine = new Line();
myLine.Stroke = System.Windows.Media.Brushes.LightSteelBlue;
myLine.StrokeThickness = 2;
myLine.X1 = e.GetTouchPoint(this).Position.X;
myLine.Y1 = e.GetTouchPoint(this).Position.Y;
lines.Add(myLine);
}
public void MainCanvas_TouchUp(object sender, TouchEventArgs e)
{
lines[lines.Count - 1].X2 = e.GetTouchPoint(this).Position.X;
lines[lines.Count - 1].Y2 = e.GetTouchPoint(this).Position.Y;
MainCanvas.Children.Add(lines[lines.Count - 1]);
}
}
My WPF app has a tab control in a main (parent) window. Each tab contains a UserControl to declutter the xaml and code behind of the main window. I'm using entity framework drag-and-drop techniques outlined in this post by Julie Lerman. I am not using MVVM. The app performs CRUD operations on a single table/entity. Multiple lookup table/entities are joined to the primary table using foreign key references. The parent window has a class level _context variable referencing a new instance of my entity container which I think of as my database connection (on steroids). How do I pass _context from the main window to the user controls?
Creating a Context property referencing _context on the parent window seemed like a good idea. The problem is that breaks my parent window xaml. It no longer compiles because I'm accessing Context in the UserControl's loaded event. I'm guessing the control is compiled before the parent window causing a null reference (from the child to the parent Context) exception in the main window xaml. Everything works fine if I just create a new _childContext variable in the UserControl but that seems like an error prone solution.
My reason for needing the _context reference is to use it to populate my drop-down lookup lists. All of the bound UserControl fields have their DataContext set in the parent window. The parent DataContext references the single entity/table that CRUD is being performed against. This DataContext does not include my lookup tables. That is why I think I need a reference to _context so I can use it to generate LINQ statements inside the UserControl to populate my lookup lists.
Thanks in advance.
If you set the parent windows DataContext to your _context variable your childlren will automatically inherit it into their DataContext. Then just change your main windows binding to point to the part of your DataContext that its interested in and have you children use the part they are interested in.
I found the answer to my own question and it's really quite simple. Dragging an entity from the Data Sources window to a UserControl auto-generates this code:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
//Do not load your data at design time.
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
//Load your data here and assign the result to the CollectionViewSource.
System.Windows.Data.CollectionViewSource myCollectionViewSource = (System.Windows.Data.CollectionViewSource)this.Resources["Resource Key for CollectionViewSource"];
myCollectionViewSource.Source = your data
}
}
I realized the problem was that I had commented out those generated lines and had not wrapped my data access code in this:
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) {}.
To make everything work I reverted back to referencing a Context property on the parent window. Now my UserControl_Loaded event looks something like this:
// Do not load your data at design time.
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
Window parentWindow = Window.GetWindow(this);
MainWindow mainWindow = (MainWindow)parentWindow;
MyEntities context = mainWindow.Context;
var lookupList = from c in context.MyEntity
select c;
System.Windows.Data.CollectionViewSource myEntitiesViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("myEntitiesViewSource")));
// Load data by setting the CollectionViewSource.Source property:
myEntitiesViewSource.Source = lookupList;
The GetIsInDesignMode check corrected the xaml compile (null reference) exception in the MainWindow designer. Problem solved.
I'm just getting started with Linq-to-SQL and data binding in WPF, most of which works like a dream so far!
I've got (what I though was) a common scenario:
a) Query list of records from a table via datacontext and bind to the current user control
this.DataContext = db.ClientTypes;
b) Have the user see a bound ListView and some bound detail controls to make changes to the existing records, with a db.SubmitChanges(ConflictMode.FailOnFirstConflict); to push the changes back to the DB. No problem.
c) User wants to add a new record, so we:
ClientType ct = new ClientType();
ct.Description = "<new client type>";
db.ClientTypes.InsertOnSubmit(ct);
However at this point I dont want to call db.SubmitChanges as I want the user to be able to update the properties of the object (and even back out of the operation entirely), but I want them to be able to see the new record in the bound ListView control. Thinking I just needed to re-run the query:
ClientType ct = new ClientType();
ct.Description = "<new client type>";
db.ClientTypes.InsertOnSubmit(ct);
// Rebind the WPF list?
this.DataContext = db.ClientTypes;
listView1.SelectedItem = ct;
listView1.ScrollIntoView(ct);
However this doesn't work, the newly created record is not part of the returned list. I'm not sure if this is because of caching within L2S or if I'm just going about this the wrong way. Is there a better way to accomplish this?
Thanks.
Instead of setting your Control.DataContext = db.ClientTypes, store db.ClientTypes somewhere else and bind to an ObservableCollection that wraps it.
var somewhereElse = db.ClientTypes;
var toBind = new ObservableCollection<ClientType>(somewhereElse);
toBind.CollectionChanged += (object sender, NotifyCollectionChangedEventArgs e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
types.InsertAllOnSubmit<AddressType>(e.NewItems.Cast<AddressType>());
};
this.DataContext = toBind;
Then, when the user wants to add a new item:
ObservableCollection<ClientType> toBind = this.DataContext as ObservableCollection<ClientType>;
System.Diagnostics.Debug.Assert(toBind != null);
ClientType ct = new ClientType();
ct.Description = "<new client type>";
toBind.Add((ct);
Calling toBind.Add will cause the CollectionChanged event handler above to call InsertOnSubmit on the original Table instance, so you can call SubmitChanges() when convenient. Obviously, you'd probably want to do the same with Remove ...
Hope that helps :)
It may be worth looking into the MVVM pattern. In MVVM you have a ViewModel which wraps your Model, so you would have a ClientTypeViewModel class.
public class ClientTypeViewModel : INotifyProperyChanged
{
public ClientTypeViewModel(ClientType dataModel)
{
this.dataModel = dataModel;
}
public string Description
{
get { return this.dataModel.Description; }
set
{
this.dataModel.Description = value;
// Raise PropertyChanged event
}
}
private ClientType dataModel;
}
And something like an ApplicationView model, which would contain an ObservableCollection of ClientTypeViewModels.
public ApplicationViewModel
{
public ObservableCollection<ClientTypeViewModel> ClientTypes { get; private set; }
}
You then bind to ApplicationViewModel.ClientTypes instead of the plain data model. This way, your view will be automatically updated whenever a new item is added to ClientTypes, or a property is changed on the ClientType view model. ApplicationViewModel can listen for changes on the ClientTypes collection and automatically add newly added items to the DataContext.
You may think it's overkill for your application, I don't know - but MVVM is definitely somthing worth learning. If it feels like you're struglling or fighting with WPF, MVVM is likely where to look ;)
Look at CreateBindingList.
I think it's just because you're assigning the same reference to the DataContext. Hence, WPF doesn't see the need to refresh the binding. The easiest way around this is to:
// rebind
this.DataContext = null;
this.DataContext = db.ClientTypes;