Search code examples
c#wpfclassflowdocumentadventure

Dynamic Classes Handling Events and Label Creation Text Adventure C# WPF


I am currently doing a text game that is based around rooms. I am rather inexperienced at C# so any solutions would most likely need some examples or some sources to help. That being said, my issue is that each time I make a new "room" I need to redo a lot of work and I want to know how I could make a Room class to do what I want.

So basically, each time I create a room I do the same: 1. Initialize some Run that contain my text. 2. Initialize some clickable labels that represent my navigation events. 3. Initialize said events that match the label.

All of this is rather time consuming, each time I have to redo it all and it's prone to mistakes.

It's done in C# WPF using FlowDocument since it's a Text Adventure Game

Here's my code:

{
    public MainWindow()
    {
        InitializeComponent();
        StartingArea();
        myFlowDocument.Blocks.Add(myParagraph);
    }
    Paragraph myParagraph = new Paragraph();
    Paragraph myParagraph2 = new Paragraph();
    public void StartingArea()
    {

        InlineLabel lStartingAreaLook = new InlineLabel("look.");
        lStartingAreaLook.MouseDoubleClick += new MouseButtonEventHandler(lStartingAreaLook_Click);
        ModInlineUIContainer lStartingArea_Look = new ModInlineUIContainer(lStartingAreaLook);

        object[] pStartingRoom = { tStartingText, lStartingArea_Look };
        AddInline(pStartingRoom);
        this.Content = myFlowDocument;
        void ClearParagraph()
        {
            foreach (Inline run in myParagraph.Inlines.ToList())
            {
                myParagraph.Inlines.Remove(run);
            }
        }
        void lStartingAreaLook_Click(object sender, MouseButtonEventArgs e)
        {
            ClearParagraph();
            StartingAreaLook();
        }
        void StartingAreaLook()
        {

            InlineLabel lStartingAreaLook_Speak = new InlineLabel("speak");
            lStartingAreaLook_Speak.MouseDoubleClick += new MouseButtonEventHandler(lStartingAreaLook_Speak_Click);
            ModInlineUIContainer cStartingAreaSpeak = new ModInlineUIContainer(lStartingAreaLook_Speak);

            InlineLabel lStartingArea_Use = new InlineLabel("use");
            lStartingArea_Use.MouseDoubleClick += new MouseButtonEventHandler(lStartingAreaLook_Use_Click);
            ModInlineUIContainer cStartingAreaUse = new ModInlineUIContainer(lStartingArea_Use);

            object[] pStartingArea2 = { tStartingAreaLook, cStartingAreaSpeak, tStartingAreaLook2, cStartingAreaUse, tStartingAreaLook3 };
            AddInline(pStartingArea2);
            void lStartingAreaLook_Speak_Click(object sender, MouseButtonEventArgs e)
            {
                ClearParagraph();
                StartingAreaSpeak();
                //myParagraph.Inlines.Add(tStartingAreaLook);
            }
            void lStartingAreaLook_Use_Click(object sender, MouseButtonEventArgs e)
            {
                ClearParagraph();
                StartingAreaUse();
                //myParagraph.Inlines.Add(tStartingAreaLook);
            }
            void StartingAreaSpeak()
            {

                InlineLabel lStartingAreaSpeak_Look = new InlineLabel("look");
                lStartingAreaSpeak_Look.MouseDoubleClick += new MouseButtonEventHandler(lStartingAreaSpeak_Look_Click);
                ModInlineUIContainer cStartingAreaSpeak_Look = new ModInlineUIContainer(lStartingAreaSpeak_Look);

                InlineLabel lStartingAreaSpeak_Use = new InlineLabel("use");
                lStartingAreaSpeak_Use.MouseDoubleClick += new MouseButtonEventHandler(lStartingAreaSpeak_Use_Click);
                ModInlineUIContainer cStartingAreaSpeak_Use = new ModInlineUIContainer(lStartingAreaSpeak_Use);

                object[] pStartingAreaSpeak = { tStartingAreaSpeak, cStartingAreaSpeak_Look, tStartingAreaSpeak2, cStartingAreaSpeak_Use, tStartingAreaSpeak3 };
                AddInline(pStartingAreaSpeak);
                void lStartingAreaSpeak_Look_Click(object sender, MouseButtonEventArgs e)
                {
                    ClearParagraph();
                    StartingAreaLook();
                    //myParagraph.Inlines.Add(tStartingAreaLook);
                }
                void lStartingAreaSpeak_Use_Click(object sender, MouseButtonEventArgs e)
                {
                    ClearParagraph();
                    StartingAreaUse();
                    //myParagraph.Inlines.Add(tStartingAreaLook);
                }
            }
            void StartingAreaUse()
            {

                Run tStartingArea_Use = new Run($@"{sUse}");

                InlineLabel lStartingArea_Use_Restart = new InlineLabel("Restart");
                lStartingArea_Use_Restart.MouseDoubleClick += new MouseButtonEventHandler(lStartingArea_Use_Restart_Click);
                ModInlineUIContainer cStartingArea_Use_Restart = new ModInlineUIContainer(lStartingArea_Use_Restart);

                object[] pStartingAreaUse = { tStartingArea_Use, cStartingArea_Use_Restart };

                AddInline(pStartingAreaUse);


                void lStartingArea_Use_Restart_Click(object sender, MouseButtonEventArgs e)
                {
                    ClearParagraph();
                    StartingArea();
                    //myParagraph.Inlines.Add(tStartingAreaLook);
                }
            }
        }
    }
    private void RemoveDoubleClickEvent(Label b)
    {
        FieldInfo f1 = typeof(Control).GetField("EventDoubleClick",
            BindingFlags.Static | BindingFlags.NonPublic);
        object obj = f1.GetValue(b);
        PropertyInfo pi = b.GetType().GetProperty("Events",
            BindingFlags.NonPublic | BindingFlags.Instance);
        EventHandlerList list = (EventHandlerList)pi.GetValue(b, null);
        list.RemoveHandler(obj, list[obj]);
    }
    public void AddInline(object[] inline)
    {
        foreach(dynamic element in inline)
        {
            myParagraph.Inlines.Add(element);
        }
    }
    public void RemoveInline(object[] inline)
    {
        foreach (dynamic element in inline)
        {
            myParagraph.Inlines.Add(element);
        }
    }
}

Now what I'd like to be able to do, is create a class that I can use each time I make a new room. The problem is that I need to create events and an unknown number of labels for each of them.

I'd basically like to be able to do something similar to this:

{
Public void Livingroom()
{

    Room Livingroom = new Room();
    SetupRoom(Room.Livingroom, 3, 2);
    //Whereas the first is the room object, the second is the amount of Run, the third is the amount of labels
    object[] LivingroomParagraph = { Livingroom.Run1, Livingroom.Run2, Livingroom.Label1, Livingroom.Run3, Livingroom.Label2 };
    AddInline(LivingroomParagraph);
    void lLivingroom1(object sender, MouseButtonEventArgs e)
    {
        //Do something
    }

    void lLivingroom2(object sender, MouseButtonEventArgs e)
    {
        //Do something

    }

}

}

So basically I need a constructor that will dynamically generate especially those parts:

Run tStartingArea_Use = new Run($@"{sUse}");

                InlineLabel lStartingArea_Use_Restart = new InlineLabel("Restart");
                lStartingArea_Use_Restart.MouseDoubleClick += new MouseButtonEventHandler(lStartingArea_Use_Restart_Click);
                ModInlineUIContainer cStartingArea_Use_Restart = new ModInlineUIContainer(lStartingArea_Use_Restart);

This is rather complicated, so I hope I'll get some help!


Solution

  • I don't think you need to go to the trouble of creating a class to make this easier for you. You're really just currently repeating a lot of code that you can nicely wrap up into a single function.

    The key thing that you're repeating is the creation of your ModInlineUIContainer object. I'd suggest you make a method to do the hard work for you:

    private ModInlineUIContainer CreateContainer(string text, Action mouseDoubleClick)
    {
        var label = new InlineLabel(text);
        var container = new ModInlineUIContainer(label);
    
        label.MouseDoubleClick += (s, e) => mouseDoubleClick();
    
        return container;
    }
    

    The .MouseDoubleClick += (s, e) => syntax alleviates the need to create a separate method and the use of Action mouseDoubleClick let's you pass in whatever action you want performed when the MouseDoubleClick is raised.

    One other refactoring I did was to remove the ClearParagraph(); method call from each event handler and just put it in to the start of each StartingArea*() method.

    That means that I can replace this code:

    InlineLabel lStartingAreaLook_Speak = new InlineLabel("speak");
    lStartingAreaLook_Speak.MouseDoubleClick += new MouseButtonEventHandler(lStartingAreaLook_Speak_Click);
    ModInlineUIContainer cStartingAreaSpeak = new ModInlineUIContainer(lStartingAreaLook_Speak);
    
    void lStartingAreaLook_Speak_Click(object sender, MouseButtonEventArgs e)
    {
        ClearParagraph();
        StartingAreaSpeak();
    }
    

    ...with this:

        ModInlineUIContainer speak = CreateContainer("speak", () => StartingAreaSpeak());
    

    I then changed the AddInLine method's parameter to have the signature params object[] inline so that I could call it directly without creating an array first.

    Now the rest of the code falls out nicely. It looks like this:

        public MainWindow()
        {
            InitializeComponent();
            StartingArea();
            myFlowDocument.Blocks.Add(myParagraph);
            this.Content = myFlowDocument;
        }
    
        Paragraph myParagraph = new Paragraph();
    
        private ModInlineUIContainer CreateContainer(string text, Action mouseDoubleClick)
        {
            var label = new InlineLabel(text);
            var container = new ModInlineUIContainer(label);
    
            label.MouseDoubleClick += (s, e) => mouseDoubleClick();
    
            return container;
        }
    
        public void StartingArea()
        {
            ClearParagraph();
            var look = CreateContainer("look", () => StartingAreaLook());
            AddInline(tStartingText, look);
        }
    
        void StartingAreaLook()
        {
            ClearParagraph();
            var speak = CreateContainer("speak", () => StartingAreaSpeak());
            var use = CreateContainer("use", () => StartingAreaUse());
            AddInline(tStartingAreaLook, speak, tStartingAreaLook2, use, tStartingAreaLook3);
        }
    
        void StartingAreaSpeak()
        {
            ClearParagraph();
            var look = CreateContainer("look", () => StartingAreaLook());
            var use = CreateContainer("use", () => StartingAreaUse());
            AddInline(tStartingAreaSpeak, look, tStartingAreaSpeak2, use, tStartingAreaSpeak3);
        }
    
        void StartingAreaUse()
        {
            ClearParagraph();
            var tStartingArea_Use = new Run($"{sUse}");
            var restart = CreateContainer("Restart", () => StartingArea());
            AddInline(tStartingArea_Use, restart);
        }
    
        void ClearParagraph()
        {
            foreach (Inline run in myParagraph.Inlines.ToList())
            {
                myParagraph.Inlines.Remove(run);
            }
        }
    
        public void AddInline(params object[] inline)
        {
            foreach (dynamic element in inline)
            {
                myParagraph.Inlines.Add(element);
            }
        }
    

    That should be a lot less code repetition.


    If you want to get all funky monkey, then you could try this:

    public MainWindow()
    {
        InitializeComponent();
    
        myFlowDocument.Blocks.Add(myParagraph);
        this.Content = myFlowDocument;
    
        _areas = new Dictionary<string, Func<object[]>>()
        {
            { "start", () => new object[] { CreateContainer("look") } },
            { "look", () => new object[] { tStartingAreaLook, CreateContainer("speak"), tStartingAreaLook2, CreateContainer("use"), tStartingAreaLook3 } },
            { "speak", () => new object[] { tStartingAreaLook, CreateContainer("look"), tStartingAreaLook2, CreateContainer("use"), tStartingAreaLook3 } },
            { "use", () => new object[] { new Run($"{sUse}"), CreateContainer("start") } },
        };
    
        Starting("start");
    }
    
    private void Starting(string key)
    {
        foreach (Inline run in myParagraph.Inlines.ToList())
        {
            myParagraph.Inlines.Remove(run);
        }
        foreach (dynamic element in _areas["look"]())
        {
            myParagraph.Inlines.Add(element);
        }
    }
    
    Paragraph myParagraph = new Paragraph();
    
    Dictionary<string, Func<object[]>> _areas;
    
    private ModInlineUIContainer CreateContainer(string text)
    {
        var label = new InlineLabel(text);
        var container = new ModInlineUIContainer(label);
    
        label.MouseDoubleClick += (s, e) => Starting(text);
    
        return container;
    }