I have a WPF xaml template and associated code behind with a variety of controls. The user can move these controls around so that one has the layout that one desires. However, once the user restarts the program, the controls return to their original locations. How do I make it so that the user can save the layout?
You can save, reload the layout (xaml):
After the user changes the layout [on Window Closing event], You can save a XAML file base changed layout using XamlWriter static class. In fact you serialize the container control and save it in a file.
Also you need some codes [in the window constructor after InitializeComponent()] to reload serialized layout of the container control [and its controls] from the file.
I put a sample (wrote by Matt Searles), here:
<StackPanel>
<WrapPanel x:Name="wrapPanel1" Height="200"></WrapPanel>
<Button Click="AddButton">Add Button</Button>
<Button Click="SaveButtons">Save Buttons</Button>
<Button Click="ReloadButtons">Reload Buttons</Button>
</StackPanel>
Code behind:
/// <summary>
/// Add a button to wrapPanel1
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void AddButton(object sender, RoutedEventArgs e)
{
// Create the Button.
Button button = new Button();
button.Height = 50;
button.Width = 100;
button.Background = Brushes.AliceBlue;
button.Content = "Click Me";
wrapPanel1.Children.Add(button);
}
/// <summary>
/// Save wrapPanel1 to AA.xaml
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SaveButtons(object sender, RoutedEventArgs e)
{
StringBuilder outstr = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
settings.NewLineOnAttributes = true;
XamlDesignerSerializationManager dsm = new XamlDesignerSerializationManager(XmlWriter.Create(outstr, settings));
dsm.XamlWriterMode = XamlWriterMode.Expression;
XamlWriter.Save(wrapPanel1, dsm);
string savedControls = outstr.ToString();
File.WriteAllText(#"AA.xaml", savedControls);
}
/// <summary>
/// Reload the buttons in AA.xaml
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ReloadButtons(object sender, RoutedEventArgs e)
{
StreamReader sR = new StreamReader(#"AA.xaml");
string text = sR.ReadToEnd();
sR.Close();
StringReader stringReader = new StringReader(text);
XmlReader xmlReader = XmlReader.Create(stringReader);
WrapPanel wp = (WrapPanel)System.Windows.Markup.XamlReader.Load(xmlReader);
wrapPanel1.Children.Clear(); // clear the existing children
foreach (FrameworkElement child in wp.Children) // and for each child in the WrapPanel we just loaded (wp)
{
wrapPanel1.Children.Add(CloneFrameworkElement(child)); // clone the child and add it to our existing wrap panel
}
}
/// <summary>
/// Clone a framework element by serializing and deserializing it
/// </summary>
/// <param name="originalElement"></param>
/// <returns></returns>
FrameworkElement CloneFrameworkElement(FrameworkElement originalElement)
{
string elementString = XamlWriter.Save(originalElement);
StringReader stringReader = new StringReader(elementString);
XmlReader xmlReader = XmlReader.Create(stringReader);
FrameworkElement clonedElement = (FrameworkElement)XamlReader.Load(xmlReader);
return clonedElement;
}
Related
I am trying to reuse NumberBoxes for a GridView because having the NumberBoxes embedded directly in the GridView data template causes undesirable behavior, while reusing them does not. The problem is that I keep getting exceptions. They say "No installed components were detected" on the following line (templateRoot.FindName("NumberBox") as GridViewItem).Content = item.NumberBox; in this context
/// <summary>
/// The callback for updating a container in the GridView named CardGridView.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void UpdateGridViewContainer(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase == 1)
{
Grid templateRoot = args.ItemContainer.ContentTemplateRoot as Grid;
CardItem item = args.Item as CardItem;
(templateRoot.FindName("NumberBox") as GridViewItem).Content = item.NumberBox;
TypedEventHandler<NumberBox, NumberBoxValueChangedEventArgs> handler =
(box, args) =>
{
if (!double.IsNaN(args.NewValue))
{
_viewModel.ChangeCount(args, item);
}
};
item.SetHandler(handler);
}
}
The exception is thrown when the Page that contains the GridView is left and renavigated to. I have tried nulling out the NumberBoxes when the page is left, but that did not work. Well, it appeared to before the issue cropped up again.
This is the code that nulls out the NumberBoxes
/// <summary>
/// Creates new NumberBoxes for when this Page is loaded again.
/// </summary>
private void ResetNumberBoxes()
{
foreach (CardItem card in CardGridView.Items.Cast<CardItem>())
{
card.ResetNumberBox();
}
CardGridView.ItemsSource = null;
CardGridView.Items.Clear();
}
ResetNumberBox is just setting the NumberBox to null and assigning a new one.
The exception details
System.Runtime.InteropServices.COMException
HResult=0x800F1000
Message=No installed components were detected. (0x800F1000)
Source=WinRT.Runtime
StackTrace:
at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|20_0(Int32 hr)
at ABI.Microsoft.UI.Xaml.Controls.IContentControlMethods.set_Content(IObjectReference _obj, Object value)
An update. I have removed the GridViewItem control from the DataTemplate and tried doing the following with the same result
CardItem item = args.Item as CardItem;
Grid.SetColumn(item.NumberBox, 1);
item.NumberBox.HorizontalAlignment = HorizontalAlignment.Center;
item.NumberBox.VerticalAlignment = VerticalAlignment.Center;
(templateRoot.FindName("GridViewTemplate") as Grid).Children.Add(item.NumberBox);
I also examined a heap dump right before the line that throws, and there was only one instance of the Grid, and the NumberBox had a parent of null.
I have a message extension for MS Teams (based on Bot Framework v3). When I create a ThumbnailCard and return it to user, Teams inserts name and logo of my app to card. Can I remove them somehow?
This is how I create a card
var card = new ThumbnailCard { Text = "some text", Title= "title"};
Here is a screenshot:
No, the attribution on cards created by a messaging extension can't be removed by the app. Teams will automatically show it.
The ThumbnailCard object has the constructor:
/// <summary>
/// Initializes a new instance of the ThumbnailCard class.
/// </summary>
/// <param name="title">Title of the card</param>
/// <param name="subtitle">Subtitle of the card</param>
/// <param name="text">Text for the card</param>
/// <param name="images">Array of images for the card</param>
/// <param name="buttons">Set of actions applicable to the current
/// card</param>
/// <param name="tap">This action will be activated when user taps on
/// the card itself</param>
public ThumbnailCard(string title = default(string), string subtitle = default(string), string text = default(string), IList<CardImage> images = default(IList<CardImage>), IList<CardAction> buttons = default(IList<CardAction>), CardAction tap = default(CardAction))
{
Title = title;
Subtitle = subtitle;
Text = text;
Images = images;
Buttons = buttons;
Tap = tap;
CustomInit();
}
Have you tried with images = new List<CardImage>() ? same thing for buttons
I have some csv files that I try to read and then divide them in two columns or list and plot them in a mathematical chart.
The csv files contains two columns, one for voltage and one for time. I read the csv files and use Interactive Data display
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Controls;
namespace TWord.Pages
{
/// <summary>
/// Interaction logic for Pico.xaml
/// </summary>
public partial class Pico : Page
{
/// <summary>
/// Members
/// </summary>
int numEvent;
StreamReader myCSVStream;
int currentSlide;
ListOfList<double> AllEventsList;
/// <summary>
/// Constructor
/// </summary>
public Pico()
{
InitializeComponent();
}
#region Button Event
/// <summary>
/// Implement the button function to read the csv files.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnCSV_Browser_Click(object sender, RoutedEventArgs e)
{
// Creates a null stream
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Multiselect = openFileDialog.RestoreDirectory = true;
// Initial directory to open to
openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
// only open the csv files
openFileDialog.Filter = "csv files (*.csv)|*.csv|All files (*.*)|*.*";
// Applying filter index
openFileDialog.FilterIndex = 2;
// Create a new instance of list of lists and
// run the ReadCSV for each of files.
if (openFileDialog.ShowDialog() == true)
{
// Names of the files imported
string[] filePath = openFileDialog.FileNames;
// Path of the file
numEvent = openFileDialog.FileNames.Length;
AllEventsList = new ListOfList<double>();
foreach (var address in filePath)
{
ReadCSV(address);
}
}
// only if thr browser had done the work
//if (AllEventsList != null)
// DrawPlot(0);
}
#endregion
#region Help Functions
/// <summary>
/// Help function to make graphs and reading the csv files.
/// </summary>
private void ReadCSV(string s)
{
string line;
// new stream to read each line
myCSVStream = new StreamReader(s);
// one list for voltage
List<double> voltage = new List<double>();
// one list for time
List<double> time = new List<double>();
// reading whole csv file and split it into two columns
while ((line = myCSVStream.ReadLine()) != null)
{
try
{
string[] parm = line.Trim().Split(',');
voltage.Add(double.Parse(parm[0], CultureInfo.InvariantCulture));
time.Add(double.Parse(parm[1], CultureInfo.InvariantCulture));
}
catch { }
}
// add it to the list of lists.
AllEventsList.Add(voltage);
AllEventsList.Add(time);
// Draw the first plot
DrawPlot(0);
}
#endregion
#region
/// <summary>
/// Drawing the plot for the CSVs
/// </summary>
private void DrawPlot(int i)
{
// Array for Voltage
double[] voltage = AllEventsList[2 * i].ToArray();
// Array for time
double[] time = AllEventsList[2 * i + 1].ToArray();
//plot to the linegraph
linegraph.Plot(time,voltage,);
}
#endregion
}
}
The XAML file to this is according to :
<GroupBox Grid.Row="1" Grid.Column="0" Header="Sampled points" Style="{StaticResource GroupboxStyle}" FontFamily="{StaticResource LatoThin}" Foreground="{StaticResource TercoTextBrush}">
<d3:Chart BottomTitle="Time" LeftTitle="voltage" Margin="-10" Style="{DynamicResource ChartStyle}">
<d3:LineGraph x:Name="linegraph" Description="Simple linegraph" Stroke="Blue" StrokeThickness="2" Margin="-19,0,0,-23"/>
</d3:Chart>
</GroupBox>
The result is that the line itself sticks out of the graph grid. as image below: Ive been sitting here and tried to modify the style of the graph with no luck. Anyone had this problem? The reason I use Interactive Data display is that it is very simple to use. Just needs two arrays and you are done. If you have any better suggestions that is Free of charge I would be happy to know about it. but not Oxyplot . I couldn't make it work
Your margin for your LineGraph XAML markup is shifting the line.
Margin is specified as:
<object Margin="left,top,right,bottom"/>
So a margin of -19,0,0,-23 specifies a shift to a xaml element 19 units to the left and 23 units down. Which is what you are seeing.
Change:
<d3:LineGraph x:Name="linegraph" Margin="-19,0,0,-23" Description="Simple linegraph" Stroke="Blue" StrokeThickness="2" />
To:
<d3:LineGraph x:Name="linegraph" Margin="0" Description="Simple linegraph" Stroke="Blue" StrokeThickness="2" />
So I've been digging into the whole SuspendLayout()/ResumeLayout() logic for a while now and I've been trying to implement it in the most efficient manner. One question I'm unable to find an answer for is that of which I've asked in the title : Does the BringToFront() method go before or after you call ResumeLayout()? Does it matter? My initial thought process is no, it doesn't matter, because it's only changing the z-index of the control and not effecting the control's layout, but I just want to be sure.
Here's block of code from my project where my question comes into play:
Note:This project runs on a Motorola MC65 mobile device and uses .net compact 3.5 framework
/// <summary>
/// Initializes the <see cref="VerifyReplacementPanel"/> class
/// </summary>
/// <param name="hostControl">The panel this control is being added to</param>
/// <param name="original">The product being replaced</param>
/// <param name="replacement">The product replacing with</param>
/// <param name="logHelper">The log helper interface</param>
public VerifyReplacementPanel(Control hostControl, ProductModel original, ProductModel replacement, ILogHelper logHelper)
{
hostControl.SuspendLayout();
SuspendLayout();
HostControl = hostControl;
Product = original;
Replacement = replacement;
_logHelper = logHelper;
Size = Size.FullScreen();
// original product panel
var panOrig = new blkPan(472, 75) { Location = new Point(4, 147), BackColor = Color.White };
var originalProductPanel = new ProductPanelArrayModel(panOrig, _logHelper);
originalProductPanel.AddPanelWithPic(original);
Controls.Add(panOrig);
// replacement product panel
var panRepl = new blkPan(472, 75) { Location = new Point(4, panOrig.B + 100), BackColor = Color.White };
var replacementProductPanel = new ProductPanelArrayModel(panRepl, _logHelper);
replacementProductPanel.AddPanelWithPic(replacement);
Controls.Add(panRepl);
// no button
var btnNo = new PushButton("No", ObjectName, true) { Location = new Point(38, Bottom - 93 - 36) };
btnNo.Click += btnNo_Click;
Controls.Add(btnNo);
_btnNoTop = btnNo.Top;
// yes button
var btnYes = new PushButton("Yes", ObjectName, true) { Location = new Point(259, btnNo.Top) };
btnYes.Click += btnYes_Click;
Controls.Add(btnYes);
ResumeLayout(false);
HostControl.Controls.Add(this);
BringToFront();
HostControl.ResumeLayout();
}
My question is interested in this section :
ResumeLayout(false);
HostControl.Controls.Add(this);
BringToFront();
HostControl.ResumeLayout();
Also, am I even using it correctly? Thank you for your time.
I'm currently facing a problem with printing a Plotmodel to an XPS-File.
What I have so far:
/// <summary>
/// Plotmodel to XPS-File
/// </summary>
/// <param name="model">The model</param>
/// <param name="fileName">The fileName</param>
/// <param name="width">Width of the model</param>
/// <param name="height">Height of the model</param>
public void Export(PlotModel model, string fileName, double width, double height)
{
try
{
using (Package xpsPackage = Package.Open(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
using (var doc = new XpsDocument(xpsPackage))
{
var g = new Grid();
var p = new OxyPlot.Wpf.Plot { Model = model }; //Problem?
g.Children.Add(p);
var size = new Size(width, height);
g.Measure(size);
g.Arrange(new Rect(size));
g.UpdateLayout();
if (xpsdw == null) //XpsDocumentWriter xpsdw
xpsdw = XpsDocument.CreateXpsDocumentWriter(doc);
}
if (xpsdw != null)
{
xpsdw.Write(g);
}
}
}
}
This Code works fine (once), but the Problem is: No matter how often you use this method, you will always have just one Page with data. So if you want to print a second Plotmodel into the XPS-File the old one is deleted and you can only see the new one.
So the Question is:
Do you have an idea, how to append the new Plotmodel to the old XPS-File without overwriting it?
I also tried to use:
XpsExporter.Export(model, fileName, width, height);
instead of my function, but this didn't worked either.
If you know in advance that you will be writing multiple plots to a single file, you should look into VisualsToXpsDocument.
If you truly must append additional pages to an existing document on disk, it will be more difficult. You will need to dig down through the existing document to get to a FixedDocument (a child of the FixedDocumentSequence). Once you have the FixedDocument, you can add a new PageContent object to it. There is an example near the bottom of this thread.