GTK# sctructure of application - c#

I am newbie in desktop application programs and I want to create an applications which will use only 1 window for all things. First of all I create a simple LOGIN form with "Login" button and 2 text fields. When user successfully logins window changes it's form. I pinned a picture of the, lets say, MAIN form, where user can switch between "screens" (I don't know how to properly name it) by clicking 1..4 buttons at the right. And we have the top panel with profile photo and nickname which persist all the time program still in the MAIN form.
So, the main question is: HOW should I do it? Which widgets I should use and how does dynamic content in GTK+ (and others, like Qt etc.) should be implemented? So I want answers, links to topics which covers this situation, guides and lessons. Anything will help.
I read a lot of documentation but it seems that I can't find proper information just because I don't know how to formulate my problem for search query.
Thank you, I hope I'll find some help.
P.S. I am gonna use C# and GTK# for this application.

You only need to create various boxes (Gtk.VBox or Gtk.HBox), and hide/show them as you need to. I did not know about Gtk.Stack, but I bet it uses the same principle, maybe with some optimization.
class UniqueWindowView: Gtk.Window
{
void Build()
{
var vbMainBox = new Gtk.VBox();
this.vbLoginPage = this.BuildLoginPage();
this.vbNotebook = this.BuildNotebook();
vbMainBox.PackStart( this.vbLoginPage, true, true, 100 );
vbMainBox.PackStart( this.vbNotebook, true, true, 5 );
vbMainBox.Show();
this.Add( vbMainBox );
this.SetSizeRequest( 600, 400 );
this.Show();
}
// ...
public Gtk.VBox vbLoginPage {
get; private set;
}
public Gtk.VBox vbNotebook {
get; private set;
}
public Gtk.Notebook nbNotebook {
get; private set;
}
public Gtk.Button btLogin {
get; private set;
}
}
Once you have this scheme running, you have to prepare to show or hide the appropriate boxes.
class UniqueWindowCtrl
{
public UniqueWindowCtrl()
{
this.view = new UniqueWindowView();
this.view.DeleteEvent += (o, args) => this.Quit();
this.view.btLogin.Clicked += (sender, e) => this.ShowNotebook();
}
public void Start()
{
this.ShowLogin();
}
public void ShowLogin()
{
this.view.vbLoginPage.Show();
this.view.nbNotebook.Hide();
}
public void ShowNotebook()
{
this.view.vbLoginPage.Hide();
this.view.nbNotebook.Show();
}
void Quit()
{
this.view.Hide();
Gtk.Application.Quit();
}
UniqueWindowView view;
}
You can find the whole source code for a unique window in GTK# here.
Hope this helps.

I suggest you take a look at GtkStack. This will enable you to have multiple widget layouts in a single window and provides you with an easy way to change which widgets are shown. GtkStack is commonly used with StackSwitcher. However normal buttons will work just as well. Can I also suggest you use Glade to lay out your widgets? This will give you a presentation of how your widgets sets will look.
Edit: GtkStack does not appear to be available for Gtk#.

Related

Save/Open Dynamically Created Controls In a Form

I am trying to develop a program in which it could create forms and add controls to it at runtime.
It also should be able to save, (Open and Edit) the forms created with the new controls added it at Runtime.The Application starts In the Main form.
CODE BEHIND MAIN Form
private void Btn_CREATE_FORM_Click(object sender, EventArgs e)
{
Form_Properties fp = new Form_Properties();
fp.Show();
}
private void BTn_ADD_BTN_Click(object sender, EventArgs e)
{
/// WHAT CODE SHOULD I ENTER TO ADD BUTON TO NEW FORM
}
Basically the main form is used to create/open/save new forms and add controls to it.
When the user clicks on Create New Form button the user will be presented with the following form (FORM_PROPERTIES) in which the user can customize the name, width and height of the new form.
CODE BEHIND FORM_PROPERTIES Form
public partial class Form_Properties : Form
{
public Form_Properties()
{
InitializeComponent();
}
String form_name;
int form_width;
int form_height;
private void Btn_OK_Click(object sender, EventArgs e)
{
form_name = TBox_NAME.Text;
form_width = Convert.ToInt32(TBox_WIDTH.Text);
form_height = Convert.ToInt32(TBox_HEIGHT.Text);
New_Form nf = new New_Form();
nf.Text = form_name;
nf.Width = form_width;
nf.Height = form_height;
nf.Show();
}
}
The following image shows what happens at runtime based on the code I have written so far.
ISSUES
Need help to Write Code
To add controls to new form created.
To Save/Open/Edit Functionalities.
I also need to know the method to access properties of added controls at runtime.
eg: If the user adds a text box to the NEW FORM and decides to type some text in it, I need a method to save that text.
Is there a way for me to name the added controls?
It seems you want to build some kind of WinForms' form designer. Your program would be similar to Glade (though Glade is much more powerful).
I'm afraid the question is too broad, though. There are many questions to answer, for example, how do you describe the created interface.
While Glade uses XML, you can choose another format, such as JSON. Let's say that you have a TextBox with the word "example" inside it.
{ type:"textbox" text:"example" }
It seems you want to add your components to the form as in a stack. Maybe you could add its position. For example, a form containing a label
("data"), a textbox ("example"), and a button ("ok"), would be:
{
{ pos:0, type:"label", text:"data" },
{ pos:1, type:"textbox", text:"example" },
{ pos:2, type:"button", text:"ok" },
}
But this is just a representation. You need to a) store this when the form is saved, and b) load it back when the form is loaded.
For that, you will need a class representing the components, such as:
public class Component {
public override string ToString()
{
return string.Format( "position:{0}, text:{1}", this.Position, this.Text );
}
public int Position { get; set; }
public string Text { get; set; }
}
public class TextBoxComponent: Component {
public override string ToString()
{
return base.ToString() + "type:\"textbox\"";
}
}
...and so on. This is a big task, I'm afraid, with no simple answer.

Method, Delegate and Event Subscription Help in Autodesk Inventor Add-In

I'm creating an add-in for Autodesk Inventor. Basically, you define the buttons you want to add, and tell the app to add the button definitions. The problem I'm having is that when I define the 'OnExecute' method for the button definition, the button doesn't execute. I think the way I'm trying to organize the code is what's creating the problem.
I have a CustomButton class that has a delegate property that looks like this (the signature is void with an input of a NameValueMap Interface)
public class CustomButton
{
// … properties and methods that don't matter here
public ButtonDefinitionSink_OnExecuteEventHandler Execute { get; set; }
}
In the main Activate() method (what's called when Inventor starts) I create an instance of the following class to set all the button definitions and the methods that fire when they are clicked. That class looks like this:
public class CustomButtonDefinitions
{
public CustomButtonDefinitions(ref Application app)
{
_inventorApp = app;
InitializeButtonDefinitions();
}
public List<CustomButton> CustomButtons { get; set; } = new List<CustomButton>();
private void InitializeButtonDefinitions()
{
AddTestButton();
}
private void AddTestButton()
{
var testButton = new CustomButton
{
DisplayName = "test",
InternalName = "testCommand1",
Ribbon = "Assembly",
RibbonPanel = "Simplification",
IconSource = "./Assets/test.jpg",
Classification = CommandTypesEnum.kFileOperationsCmdType,
ShowText = true,
UseLargeIcon = true,
};
testButton.Execute = TestButton_Execute;
CustomButtons.Add(testButton);
}
private void TestButton_Execute(NameValueMap Context)
{
// This is where the logic of the button would go.
// For now, just something that gives me an indication it worked.
System.Windows.Forms.MessageBox.Show("Hello");
_inventorApp.ActiveDocument.Close();
}
}
Where I think the source of the error comes from is the next code (this is in the Activate():
CustomButtonDefinitions customButtonDefinitions = new CustomButtonDefinitions(ref _InventorApp);
foreach (var button in customButtonDefinitions.CustomButtons)
{
// this creates the button in Inventor
var buttonDef = button.CreateButtonDefinition(ref controlDefs);
// and this subscribes the button click event to my method
buttonDef.OnExecute += button.Execute;
}
There must be something un-subscribing my method from the button click event.
I'll be posting this in the Inventor forums as well, but wanted to check here too since I'm new to delegates and event handlers. I'm either not understanding something about delegates/events or it's something Inventor specific that I'll need some other help with.
Hopefully this is enough to give some context. Thanks in advance.
The problem was the fact that I wasn't creating the button definition in a high enough scope. I needed to create a variable above the scope of the Activate() method so the app could see it when needed.

Calling a method from a different ViewModel

I'm writing a program for managing a tool inventory and have run into a problem when I have the users mark a tool as 'fixed'.
The program should work as follows:
Using TIView, TIViewModel, TIModel:
Employee checks tool out.
Tool happens to get damaged during use.
Employee return's the tool marking it as damaged and reporting the problem.
The tool is marked as returned and locked from being check out until fixed.
Using VPRView, VPRViewModel, and VPRModel:
An inspector goes into a data grid showing all tools with problems.
The inspector corrects the problem, marks the tool as fixed, then submits the data.
The program updates the SQLite database with the inspectors ID number, their solution, marks the problem as fixed and logs the date/time of completion.
THE PROBLEM STEP:
8. The program then runs the PopulateToolInventory method from the TIViewModel to update the inventory list so that the tool is no longer locked.
Summarized:
When the inspector marks the tool as fixed the database is updated using the VPRView, VPRViewModel, and VPRModel. The method to pull the data for the tool inventory is found in the TIViewModel. How do I get the application to execute the 'PopulateToolInventory' method from the VPRViewModel after uploading the data to the database via the VPRViewModel?
Code Sample:
VPRViewModel:
public void SubmitSolution()
{
VPRModel vprm = new VPRModel();
vprm.SubmitProblemSolution(ProblemSolved, ProblemSolution, InspectorID, SelectedReport[0].ToString());
ProblemReports = vprm.RetrieveProblemReports();
InspectorID = null;
ProblemSolution = null;
ProblemSolved = false;
MessageBox.Show("Solution successfully recorded!", "Success!", MessageBoxButton.OK);
// This is where I would want to call the method from the TIViewModel to update the data grid on the TIView.
}
TIViewModel:
private DataTable _toolInventory;
public DataTable ToolInventory
{
get { return _toolInventory; }
set
{
_toolInventory = value;
NotifyOfPropertyChange(() => ToolInventory);
}
}
public void PopulateToolInventory()
{
TIModel tim = new TIModel();
ToolInventory = tim.RetrieveToolInventory();
}
ShellViewModel:
class ShellViewModel : Conductor<object>
{
public void Open_ToolInventory()
{
ActivateItem(new TIViewModel());
}
public void ViewProblemReport()
{
WindowManager wm = new WindowManager();
VPRViewModel vprvm = new VPRViewModel();
wm.ShowDialog(vprvm);
}
}
FYI: I'm using Caliburn.Micro if this helps with any solution.
Hopefully this is enough information. If not, just ask for what you need! Also, please don't eat my code alive. I'm self taught and know that I'm far from a professional developer but this is a passion of mine and I'm really enjoying it. Constructive criticism is appreciated, just please don't make me feel stupid.
Using Ed's idea in the question comments I did the following.
class ShellViewModel : Conductor<object>
{
public void Open_ToolInventory()
{
ActivateItem(new TIViewModel());
}
public void ViewProblemReport()
{
WindowManager wm = new WindowManager();
VPRViewModel vprvm = new VPRViewModel();
wm.ShowDialog(vprvm);
}
}
Was changed to:
class ShellViewModel : Conductor<object>
{
TIViewModel tivm = new TIViewModel();
VPRViewModel vprvm = new VPRViewModel();
public void OpenToolInventory()
{
ActivateItem(tivm);
}
public void ViewProblemReport()
{
WindowManager wm = new WindowManager();
wm.ShowDialog(vprvm);
tivm.PopulateToolInventory();
}
}
This runs the targeted method after the dialog is closed updating the tool inventory to reflect all the solved problems at once. You're the best, Ed!

How to handle events that happened on different page

I have a UWP Project with 2 pages so far. The MainPage.xaml is the basic layout of the app ( hamburger menu, search bar, etc.). The other part of this MainPage contains a frame into which the other page LandingPage.xaml is loaded. I want to capture the user input from an AutosuggestBox in the MainPage.xaml and show the results on LandingPage.xaml ( which is in a frame present inside MainPage.xaml).
I tried inheriting the MainPage, but that's not allowed.
While Marian's answer would certainly work, I think it's far from being 'clean' or 'good' code.
First and foremost, you should implement the MVVM pattern in your UWP apps (if you don't do it already) and use a dependency injection framework for that. A very basic, easy to understand one is MVVMLight, while a more sophisticated framework of choice could be Autofac. I advise you to start with the former, it's much quicker to wrap your head around it first.
In MVVM there's a concept that solves just your problem: messengers. I wouldn't like to get into the details here, since there already a lot of very good resources about this written by much smarter people than me. For example this article from the author of MVVMLight himself: https://msdn.microsoft.com/en-us/magazine/jj694937.aspx (I know it's from 2013 and speaks about Windows 8, but fear not, the concepts are just the same.)
The idea is that distinct ViewModels shouldn't have strict dependencies on each other - it makes unit testing (which is one of the main points of doing MVVM in the first place) hard. So in your case, you should have two ViewModels: MainViewModel and LandingViewModel. One for MainPage, and one for LandingPage, respectively. Now you should implement a handler in MainPage's code-behind for AutoSuggestBox's QuerySubmitted event and call a function in MainViewModel. In that function, you would instantiate a new message with the string coming from your AutoSuggestBox (which you can acquire either from doing data binding to it or through the event handler of QuerySubmitted, it's up to you) and send it via the Messenger. In LandingViewModel, you would subscribe to this exact message and then it's again just a matter of few lines to display the received message through data binding on LandingPage.
I know it looks like a lot of hassle for just something very basic like this, especially if you compare it to Marian's straight to the point solution. But trust me, in the long run writing clean code, nicely separated, easily unit testable ViewModels will make up for the additional effort that you have to put into them initially to make them work. After such a system is set up between two ViewModels, adding a third (which I assume you'll need to do soon) is absolutely trivial and can be done very quickly.
If you're not using MVVM I'd suggest adding x:FieldModifier="public" on the AutoSuggestBox and add a public static property to MainPage to store its instance.
MainPage.xaml.cs
public static MainPage Current { get; private set; }
public MainPage()
{
Current = this;
// Rest of your code in ctor
}
Then you can access it using
string text = MainPage.Current.NameOfYourAutoSuggestBox.Text;
Just use a simple message passing mechanism of your own, like this:
public class Messages {
public static Messages Instance { get; } = new Messages();
private readonly List<Subscription> subscriptions;
private Messages() {
subscriptions = new List<Subscription>();
}
public void Send<T>(T message) {
var msgType = message.GetType();
foreach (var sub in subscriptions)
if (sub.Type.IsAssignableFrom(msgType))
sub.Handle(message);
}
public Guid Subscribe<T>(Action<T> action) {
var key = Guid.NewGuid();
lock (subscriptions) {
subscriptions.Add(new Subscription(typeof(T), key, action));
}
return key;
}
public void Unsubscribe(Guid key) {
lock (subscriptions) {
subscriptions.RemoveAll(sub => sub.Key == key);
}
}
public bool IsSubscribed(Guid key) {
lock (subscriptions) {
return subscriptions.Any(sub => sub.Key == key);
}
}
public void Dispose() {
subscriptions.Clear();
}
}
internal sealed class Subscription {
internal Guid Key { get; }
internal Type Type { get; }
private object Handler { get; }
internal Subscription(Type type, Guid key, object handler) {
Type = type;
Key = key;
Handler = handler;
}
internal void Handle<T>(T message) {
((Action<T>)Handler).Invoke(message);
}
}
It's small and simple but it allows the subscription of different messages in parallel, separated by message type. You can subscribe, in a case similar to yours, with:
Messages.Instance.Subscribe<TextChangeArgs>(OnTextChanged);
and your other pages can send their messages using:
Messages.Instance.Send(new TextChangeArgs(...));
From all subscribers, only those interested in this specific message type will receive the message. You can (and, of course, should) also unsubscribe. Some more error handling could also be necessary in a real world scenario.
If necessary, you can add extra functionality like throttling easily (to avoid too many consecutive messages in a given time period).

Create master/detail grids using XtraGrid Entity framework

I'm a beginner regarding C#, Entity and working with grids, so although I assume what I'm asking is somewhat general, I am a little lost.
I have two tables in my SQL database: PeriodicReports and PeriodicReportGroups. Each reports group can have 1 or more periodic reports, so what I envisioned was a grid with rows that would have a + sign at the left and could be expanded, showing the related periodic reports for each group. I know this can be done because I've seen very similar examples in DevExpress support site, but although I've put time trying to understand them and apply them to my case, I still haven't figured how to do it.
This is what my code looks like. After using the Entity Framework with my tables, this class got created automatically:
public partial class ReportEntities : DbContext
{
public ReportEntities()
: base("name=ReportEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<PeriodicReportGroup> PeriodicReportGroups { get; set; }
public DbSet<PeriodicReport> PeriodicReports { get; set; }
}
I created this interface and class myself in order to only show certain columns in my grid:
public interface IPeriodicReportGroup
{
string Name { get; }
string Description { get; }
bool Active { get; set; }
}
public partial class PeriodicReportGroup : IPeriodicReportGroup
{
}
And in the other hand, I have my XtraForm with an XtraGrid:
public partial class Form1 : XtraForm
{
public Form1()
{
InitializeComponent();
InitSkinGallery();
InitGrid();
}
BindingList<IPeriodicReportGroup> gridDataList = new BindingList<IPeriodicReportGroup>();
void InitGrid()
{
var database = new ReportEntities();
var reportGroups = database.PeriodicReportGroups.ToList( );
foreach (var group in reportGroups )
gridDataList.Add( group );
gridControl.DataSource = gridDataList;
}
...
So far, I'm successfully putting the periodic report groups that I have in my DB in the grid, which is good. But I still have no clue on how exactly set up the details grid that will expand for each group, although I feel it should be something pretty standard. Can anybody offer some guidance? I'm not asking for somebody else to do my work, just something that can point me out in the right direction, because right now I'm pretty lost.
I'd also love to be able to add new groups and new details on the run by inputting data into an empty row, is this possible?
Thanks in advance!
XtraGrid support three ways to do it:
Check this link for more information.
Basically master-detail is thing that confuses, but careful reading will help you.

Categories

Resources