This is more of a theory question than anything else, can't really show much in the way of code. However the topic is C# WPF.
So I have an application beginning with a main menu where I set a bunch of variables and load in some data from files and so on ready to begin the main program. I want this to be available to the main window that is going to be opened up.
Currently in my application I have it all set up by data context, an attempt at MVVM which I am very new to. This data context is linked to the menu window and allows me to set all the necessary data.
But how do I go about making that available to the new window. As far as I am aware a new data context is like a new object but in xaml, so if I were to create a data context in the next window all the information from it would be different?
I have always had trouble with things like this in all my learning of programming, I have data in one place but there is no sound way to link it across multiple windows/classes/objects etc.
Currently I have 4 files:
1 Holding all the data classes
2 My 'ViewModel' where I have the objects of these classes, property
updates, not too much
3 My menu with the bindings and data context and its code behind which is empty apart from some button
clicks
4 The main window that is going to use the data <-- this is my problem
First of all is this a correct approach, is there something different I should be thinking about? Can I implement a data context across two windows?
So I set up the context in the mainmenu window:
<Window.DataContext>
<local:WindowFunction x:Name="Interface"/>
</Window.DataContext>
And this links in my between file which has objects of the data.
class WindowFunction
{
protected PortInfo settings = new PortInfo();
protected FileInfo import = new FileInfo();
protected ObservableCollection<PersonName> lanes = new ObservableCollection<PersonName>();
public ObservableCollection<PersonName> Lane
{
get { return lanes; }
set { lanes = value; }
}
public PortInfo Settings
{
get { return settings; }
set { settings = value; }
}
public FileInfo Import
{
get { return import; }
set { import = value; }
}
}
I want to be able to call the information from this in another window. But if I create a new context the property paths will be pointing somewhere else surely?
Related
I'm currently developing an app in WPF using the MVVM pattern ( without framework ). I use VS2019,
Each view is an UserControl
The app is connected to a local database MySQLLite.
When I start my program, I have an user connection. When the user connection is successful, it loads my object "Engine" in my global class "BaseViewModel" ( inherit all ViewModel ).
In WinForm when I create a new Form(View), I just transfer my Engine class in parameters and I save the Engine locally in my Form ( not null ) and then I have access to my value in Engine like User.
What is the best way to do it in WPF using MVVM?
I try to transfer the Engine to my ViewModel when I create but it always overwrites it later with null. Because it opens the view without parameter later and calls my constructor without parameter.
You can create Data Access Level class to load your engine from DB. For example, it will be named EngineDataAccess and it will have GetEngine() method. Next in your EngineViewModel you can implement something like this:
private readonly Engine engine;
public string EngineName
{
get {return engine.Name; }
set {engine.Name = value; OnPropertyChanged("EngineName");}
}
public EngineViewModel(EngineDataAccess engineDataAccess)
{
engine = engineDataAccess.GetEngine();
}
In your code:
EngineDataAccess engineDataAccess = new EngineDataAccess();
EngineViewModel engineViewModel = new EngineViewModel(engineDataAccess);
form.DataContext = engineViewModel;
What is not very good in this way and how it can be done better:
1) getting engine from db on creating view model process => it will be better to do it on Load view model (MVVM load data during or after ViewModel construction?)
2) using EngineDataAccess class instead of interface
3) binding datacontext in the codebehind => better to use IoC
Also, I would recommend you to use some MVVM framework like Galasoft.MVVM. It does MVVM using simpler.
I'm creating a note taking application with multiple windows to gather information. How do I save the values of multiple text boxes in multiple windows to variables on button click?
I have successfully implemented it in the MainWindow and I'm having trouble extending the paradigm across multiple windows. I have a feeling it's associated with not assigning a name to the instance of the second window, but I'm not sure how it works.
This is the code for the function that works in the MainWindow:
public static void CopyText()
{
string srText = ((MainWindow)Application.Current.MainWindow).srBox.Text;
string contactText = ((MainWindow)Application.Current.MainWindow).contactBox.Text;
string usernameText = ((MainWindow)Application.Current.MainWindow).usernameBox.Text;
string generalText = ((MainWindow)Application.Current.MainWindow).generalBox.Text;
string copyText = "";
Clipboard.SetDataObject(copyText);
}
This is the code for the function in the CreditWindow that does not work:
public static void CopyCreditText()
{
string srText = ((MainWindow)Application.Current.MainWindow).srBox.Text;
string usernameText = ((MainWindow)Application.Current.MainWindow).usernameBox.Text;
string buyerText = ((CreditWindow)Application.Current.MainWindow).buyerBox.Text;
string itemText = ((CreditWindow)Application.Current.MainWindow).itemBox.Text;
string amountText = ((CreditWindow)Application.Current.MainWindow).amountBox.Text;
string typeText = ((CreditWindow)Application.Current.MainWindow).typeBox.Text;
string reasonText = ((CreditWindow)Application.Current.MainWindow).reasonBox.Text;
string copyText = "";
Clipboard.SetDataObject(copyText);
}
When I run this code, I get an error from Visual Studio on this line:
string buyerText = ((CreditWindow)Application.Current.MainWindow).buyerBox.Text;
that states "Unable to cast object of type 'MSONotes.MainWindow' to type 'MSONotes.CreditWindow'.
The error is because Application.Current.MainWindow is just that, the main window of the entire application. It doesn't change no matter how many child windows exist. It won't become a CreditWindow.
Unless the windows have references to each other, you should handle this kind of data passing well behind the view layer. I lied, you should do that regardless, but references would allow you to keep cheating. In a properly architected application:
Those text boxes would be bound to fields in a view model
The button click would push the values to some model (shared by the other window's VMs, ideally using dependency injection)
Via an event or similar the service would notify all VMs that new data was available
They would update their own appropriate fields
The other windows view's would update automatically due to the VM update.
Basically, you are going to need to do WPF properly (with MVVM) and not hack it together like that.
We've searched all over stack overflow and similar sites for something that will work for our app, but everything gets us only halfway there.
We have an application that allows the user to drag and drop devices onto a drop canvas. Upon the device being dropped, their "router properties" are created, and you can change their name, address, add notes.
We also let the user connect lines between the devices. (We also add the router properties that are created to an observable collection).
We have tried xmlserialization, and it let us save the physical side of the device, but upon loading the xml file, it no longer has the address, notes, etc attached to any saved device, and doesn't allow for adding connections or going to its properties.
I realize that we need to somehow serialize the code behind, then add it back in to each device upon de-serializing, but we can't seem to find a way to serialize the observable collection of router properties.
Does anyone have any suggestions on the simplest way to allow us to save the canvas, children, and their code behind properties? I am attaching pictures for reference, the router properties class and I'm happy to include any code if needed. We really appreciate any help at all.
Warm Regards,
Tyler
For example
Class
public class RouterProperties : INotifyPropertyChanged
{
private ArrayList incomingConnections = new ArrayList();
private ArrayList outgoingCnnections = new ArrayList();
private bool isLocked = true;
private bool isSelected = false;
private string deviceName = "Router";
private string hostName = "Host name";
private string routerIP = "192.168.0.1";
private string note = "Notes";
private string status = "Yellow";
private BitmapImage icon;
// getters and setters removed for brevity
public ArrayList IncomingConnections
...
public ArrayList OutgoingCnnections
...
public bool IsLocked
...
public bool IsSelected
...
public string DeviceName
...
public string HostName
...
public string RouterIP
...
public string Note
...
public string Status
...
public BitmapImage Icon
...
MainWindow Class
public ObservableCollection<RouterProperties> devices = new ObservableCollection<RouterProperties>();
EDIT Code to save xaml
// De-Serialize XML to UIElement using a given filename.
public static UIElement DeSerializeXAML(string filename)
{
// Load XAML from file. Use 'using' so objects are disposed of properly.
using (System.IO.FileStream fs = System.IO.File.Open(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
return System.Windows.Markup.XamlReader.Load(fs) as UIElement;
}
}
// Serializes any UIElement object to XAML using a given filename.
public static void SerializeToXAML(UIElement element, string filename)
{
// Use XamlWriter object to serialize element
string strXAML = System.Windows.Markup.XamlWriter.Save(element);
// Write XAML to file. Use 'using' so objects are disposed of properly.
using (System.IO.FileStream fs = System.IO.File.Create(filename))
{
using (System.IO.StreamWriter streamwriter = new System.IO.StreamWriter(fs))
{
streamwriter.Write(strXAML);
}
}
}
private void menuSave_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.FileName = "UIElement File"; // Default file name
dlg.DefaultExt = ".xaml"; // Default file extension
dlg.Filter = "Xaml File (.xaml)|*.xaml"; // Filter files by extension
// Show save file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog box results
if (result == true)
{
// Save document
string filename = dlg.FileName;
SerializeToXAML(canvasMain, filename);
}
}
private void menuLoad_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.DefaultExt = ".xaml"; // Default file extension
dlg.Filter = "Xaml File (.xaml)|*.xaml"; // Filter files by extension
// Show open file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
string filename = dlg.FileName;
Canvas canvas = DeSerializeXAML(filename) as Canvas;
// Add all child elements (lines, rectangles etc) to canvas
while (canvas.Children.Count > 0)
{
UIElement obj = canvas.Children[0]; // Get next child
canvas.Children.Remove(obj); // Have to disconnect it from result before we can add it
canvasMain.Children.Add(obj); // Add to canvas
}
}
}
Unfortunately i dont see a solve for your current approach, or at least none that has come to mind.
Here are the fundamentals of the problem
Serialization Limitations of XamlWriter.Save
Run-Time, Not Design-Time Representation
The basic philosophy of what is serialized by a call to Save is that
the result will be a representation of the object being serialized, at
run-time. Many design-time properties of the original XAML file may
already be optimized or lost by the time that the XAML is loaded as
in-memory objects, and are not preserved when you call Save to
serialize. The serialized result is an effective representation of the
constructed logical tree of the application, but not necessarily of
the original XAML that produced it. These issues make it extremely
difficult to use the Save serialization as part of an extensive XAML
design surface.
Extension References are Dereferenced
Common references to objects made by various markup extension formats,
such as StaticResource or Binding, will be dereferenced by the
serialization process. These were already dereferenced at the time
that in-memory objects were created by the application runtime, and
the Save logic does not revisit the original XAML to restore such
references to the serialized output. This potentially freezes any
databound or resource obtained value to be the value last used by the
run-time representation, with only limited or indirect ability to
distinguish such a value from any other value set locally. Images are
also serialized as object references to images as they exist in the
project, rather than as original source references, losing whatever
filename or URI was originally referenced. Even resources declared
within the same page are seen serialized into the point where they
were referenced, rather than being preserved as a key of a resource
collection.
My first solution would have been to assign a GUID or id to each control and router property. however seemingly this wont work, XamlWriter.Save just doesn't preserve bindings or things of that nature.
However i think you need to attack this from a ViewModel first approach
That's to say, that your ViewModel needs to keep all the implementation properties of your visual objects, the locations and anything needed to rebuild your canvas visually. As you create each visual router you need to keep all of its relevant state somewhere
Even if the implementation details are separate from the the Router ViewModel you could serialize them both and have some sort of ID to relink them at runtime.
Though my Spidey senses tells me you should redesign the architecture a bit to put all the relevant in a single Higher-Level ViewModel, though this really all depends on what the architecture of the application is.
Maybe you could have a structure like this
[Serializable]
public class RouterAndState
{
public RouterProperties {get;set;}
Public RouterVisualState {get;set;}
}
[Serializable]
public class RouterVisualState
{
// its location (x,y) and anything else it needs to be recreated
}
If you are saving the router properties to a db the router entity really doesn't care what the visual layout of the canvas is, and its not something that really should be saved but maybe it can be saved in a related table that has a map to the routers used and a map to its layout, Ie RouterMap Table, with foreign keys to the RouterProperties and Visual Layout Configuration
The other way is to just generate the visual state from the routerProperties and auto generate the layout, this is neat but you will need implement a lot more logic to auto configure how its laid-out when loading .
However if this is a fairly simple things, just serialize it all to a file using something like the above and be done with it
I hope this helps
I'm using WPF MVVM trying to figure out what would be the best way to reload my ViewModel (entire View would work as well I suppose).
The data behind my Model is parsed out of a series of flat files stored within a directory. The location of the directory is saved in the .Settings file and can be the user via a popup window.
If the user changes updates the directory they want to use, how can I recreate my ViewModel so that the data being used is what is in the new directory?
I guess it would be akin to changing to a different database while the app is running if that is what I had as a datastore. Currently I show a message asking the user to restart the application.
Here is the important code:
public SignalViewModel()
{
_trafficSignals = new ObservableCollection<TrafficSignal>(DataAccess.TrafficSignalRepository.GetTrafficSignals());
}
public static List<TrafficSignal> GetTrafficSignals()
{
string dataStore = Properties.Settings.Default.SaveLocation;
var signals = new List<TrafficSignal>();
if (Directory.Exists(dataStore))
{
var files = Directory.GetFiles(dataStore, "CP*.SAV");
Array.Sort(files);
foreach (var file in files)
{
signals.Add(LoadFile(file));
}
}
return signals;
}
I would solve this problem with two events:
Implement the INotifyPropertyChanged in the settings.
In the TrafficSignalRepository I would then implement an event (e.g. SaveLocationChanged) which you raise after the PropertyChanged event of the settings was raised for the SaveLocation-Property
Then you can register for the SaveLocationChanged event inside of the ViewModel. In the registered event handler you just call GetTrafficSignals() again, assign the new value to the field and raise the NotifyPropertyChanged event of the ViewModel. The rest should be done for you automatically by data binding.
You can reload the saved settings using
Properties.Settings.Default.Reload();
And then call GetTrafficSignals() again?
Although having said that, I have had some issues in using the Reload method in the past...
I am developing a c# windows form application. In my application i have 3 forms (main form that has a list box and two buttons (Check in and check out), check in form and the check out form). On the main form, the list box contain user names, if a user select their name for the first time, the check in button must be enabled for the user to check in... But if the user checks in and then closes the application, when they reopen it, the button check out should be enabled and check in disabled.
I have been told to use the application/user states, but since I'm new in programming, i don't know how to implement the windows form states.
What should i do?
Thank you
There is no such thing as "Windows Forms states". You have several options to implement somthing like this, among which are:
Use a database (this makes sense if you have a varying number of users and a database server available)
Use user settings (this is a builtin mechanism of the .NET framework, but may not be suitable for lots of users)
Use a simple XML file to store the states of all users.
All three solutions require you to sort of "get into things". Write more about what you have available (database server, etc.) or whether you want a fixed number of users and I can extend this answer to help you get started.
I'm going to line out how to do number 2:
Create a little helper class that assigns a state to a user name:
public class UserState
{
public string UserName { get; set; }
public bool CheckedIn { get; set; }
public override string ToString() { return String.Format("{0}={1}", UserName, CheckedIn); }
}
This class allows you to store a user name and the checked in state and by calling ToString() get a value in the form "user=false".
Then, create a user scoped application setting (go to settings-tab of project settings and add a new setting of type System.Collections.Specialized.StringCollection) named UserStates. You can access this setting from code as Properties.Settings.Default.UserStates. It is basically a list of strings.
To add and persist a new entry you could do this:
UserState state = new UserState() { UserName = "Test", CheckedIn = false };
Properties.Settings.Default.UserStates.Add(state.ToString());
Properties.Settings.Default.Save();
The state for user "Test" (and the previously existing entries) are now stored across program restarts.
Now the idea is to build a list of users and their states when starting the program and to store this list when exiting.
Declare this as a member variable in the class:
private List<UserState> userStates = new List<UserState>();
Do the following in the form's OnLoad event:
if (Properties.Settings.Default.UserStates == null || Properties.Settings.Default.UserStates.Count == 0)
{
// Add your users to the collection initially. This is the first
// run of the application
userStates.Add(new UserState() { ... });
...
}
else
{
// Each line in the setting represents one user in the form name=state.
// We split each line into the parts and add them to the internal list.
for (int i = 0; i < Properties.Settings.Default.UserStates.Count; i++)
{
string stateLine = Properties.Settings.Default.UserStates[i];
string[] parts = stateLine.Split('=');
userStates.Add(new UserState() { UserName = parts[0].Trim(), CheckedIn = Boolean.Parse(parts[1].Trim()) });
}
}
This creates a new entry in an internal list of users for each stored line in the collection setting.
When a button is clicked, change the state in the respective UserState object in the list.
Do the following in the form's OnClose event:
// Create the collection from scratch
Properties.Settings.Default.UserStates = new System.Collections.Specialized.StringCollection();
// Add all the users and states from our internal list
foreach (UserState state in userStates)
{
Properties.Settings.Default.UserStates.Add(state.ToString());
}
// Save the settings for next start
Properties.Settings.Default.Save();
This persists the current list of user states to the setting.
Please note: I have tested this in Visual Studio now and it works. I leave the question of how to map the list box entries to the UserState objects in the internal list to you/as topic for a new question :-D
The downside of this approach: It is not very flexible - adding more states per user involves some coding.
It could be better for you to read about typed datasets and how to store/read them from XML. This gives you some sort of "database feeling" without actually having to use a database.