Professional Visual Basic 2010 and .neT 4 phần 10 pps

125 466 0
Professional Visual Basic 2010 and .neT 4 phần 10 pps

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

1154 ❘ APPENDIX B Visual BasiC PowER PaCks tools FIGURE B  2 Because Visual Basic Express Edition does not support add - ins, this application will not be updated when you install the software. To validate your installation, there are three easy items you can check. First, once the installation is complete, the help topic associated with the Interop Forms Toolkit 2.1 should open. Second, when you access the Tools menu, the fi rst item in the menu should be the option to Generate Interop Form Wrapper Classes. This menu item should be located above the standard option to Attach Process. Third, and probably most important, when you access the File menu and select the New Project dialog, you should see two new project types within the Visual Basic section, as shown in Figure B - 2. The fi rst custom project type is the VB6 Interop User Control project type. This type of project enables you to create user controls that can then be used to populate the body of an MDI window. This project type was introduced with version 2.0 of the Interop Forms Toolkit and is the solution the Visual Basic team developed to support interoperation within an MDI environment. The second project type is the VB6 InteropForm Library project. As the original project type, it was designed to enable you to create a DLL that defi nes a .NET form. After you have validated that your installation is working, the next step is to create a simple Interop Form. Creating a Simple Interop Form Select the project type shown in Figure B - 2 and rename the solution ProVB_AppB_InteropForm . Click OK to generate your source project fi les. The resulting project opens, and you can open and edit your new Windows Form. However, note that what you are creating, while it supports the Form Designer, isn ’ t a standalone executable. If you open your project properties, you ’ ll fi nd that your project will build as a DLL, not a standalone executable. Another thing to note is that as part of the generation of your project, a fi le named InteropInfo.vb is created. This fi le takes settings that might otherwise exist in your AssemblyInfo.vb fi le and places them here so they are a bit more apparent. The fi rst line references the standard COM Interop classes and turns these settings off. This is important because you won ’ t be using traditional COM Interop; you ’ ve added Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Using the Interop Forms Toolkit 2.1 ❘ 1155 a new Interop class specifically for this purpose. By moving this setting into a separate file, if you do accidentally cause the AssemblyInfo.vb file to be regenerated by Visual Studio, you’ll get a compile error. This is good because you can quickly and easily delete the newly duplicated line from AssemblyInfo.vb and not wonder why your project suddenly isn’t working correctly. Compile errors are always better than runtime errors. The other item in this file is a declaration that extends the My namespace to include the Interop Toolbox. In general, you shouldn’t make any changes to this file, but now you know what it’s doing. Opening InteropForm1.vb in the designer, you have a typical design surface for a form, on which you can add controls. Behind the scenes is the code that contains the following: Imports Microsoft.InteropFormTools <InteropForm()> _ Public Class InteropForm1 End Class Code snippet from InteropForm1 As you can see, the default class definition has been decorated with an attribute indicating that this class should be considered an InteropForm. This enables the postprocessor that is used to generate your COM wrappings to recognize which type of wrapping should be applied to this class. For now, however, go to the Form Designer, and, because this is a truly simple demo, drag a label and a TextBox control onto the display. Within the code, create the four other types of interface members you’ll want in your production code: an initializer, a property, a method, and an event (in that order). The following code is placed within your class definition: Public Sub New() ' This call is required by the Windows Form Designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. End Sub <InteropFormInitializer()> _ Public Sub New(ByVal label As String) Me.New() Label1.Text = label End Sub <InteropFormProperty()> _ Public Property TextBoxText() As String Get Return TextBox1.Text End Get Set(ByVal value As String) TextBox1.Text = value End Set End Property <InteropFormMethod()> _ Public Sub ChangeLabel(ByVal lbl As String) Label1.Text = lbl RaiseEvent CustomEvent(lbl) End Sub <InteropFormEvent()> _ Public Event CustomEvent As CustomEventSig 'Declare handler signature… Public Delegate Sub CustomEventSig(ByVal lblText As String) Code snippet from InteropForm1 For the initialization code, you’ll note that first a default New constructor is created. When you define the default New constructor, it adds the call to InitializeComponent, which handles the creation of your controls within the form. Thus, when the object is initialized, you will be able to reference the controls you have placed on the form. The next step is to create a parameterized constructor so that you can quite literally pass a parameter as part of the initialization process. Note that similar to the class itself, the exposed initialization Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 1156 ❘ APPENDIX B Visual BasiC PowER PaCks tools method has an attribute as part of its declaration. Each type of class member that is to be exposed gets an attribute matching the type of that method. Thus, for the New method, the type of the attribute is InteropFormInitializer . For this simple example, the parameterized New(ByVal label As String) simply changes the text associated with the label. Finally, although this class is defi ned in .NET syntax, COM and VB6 don ’ t allow parameterized New statements. Thus, when you reference this parameterized initializer, you ’ ll fi nd that the method name is in fact Initialize . Next, the code defi nes and exposes a public property. In this case, to help simplify the code, there isn ’ t a private member variable to hold the value; this provides an easy way for the code that creates this form to set and retrieve the value of the text box. Similarly, there is a method to allow the calling code to update the label shown on the form. Note that it has also been attributed; and after you update the label for demonstration purposes, it raises the custom event that is defi ned next. That event, called CustomEvent , is defi ned with an attribute, but the event that is defi ned must also defi ne the signature or defi nition of its handlers. In this case, the Delegate CustomEventSig handles a single parameter. This .NET code, as noted, provides a basic example of each of the primary types of Interop you ’ ll want to carry out. The next step is to generate your Interop methods. One of the key differences between an InteropForms project and an Interop User Control project is this step. Only the InteropForms project requires the generation of custom COM wrappers. To do this, access the Tools menu and select Generate InteropForm Wrapper Classes. There is no user interface; instead, the generation process will create a new directory in your project containing the InteropForm1.wrapper.vb class, as shown in Figure B - 3. FIGURE B  3 For readers developing on Vista and Windows 7: Keep in mind that registry access requires elevated permissions. You need to start Visual Studio with the Run as Administrator option on your right - click context menu. If you don ’ t, then when you attempt to automatically register your newly built DLL as a COM component, you ’ ll get an error, which Visual Studio refl ects as a Build Error. Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Using the Interop Forms Toolkit 2.1 ❘ 1157 At this point, your application is ready to be called from VB6. If you follow best practices, you’ll have the VB6 integrated development environment (IDE) installed on the machine with Visual Studio 2010. In that scenario, you can immediately go to your VB6 project and reference the necessary DLLs, both the Interop Forms Toolkit DLL and your custom DLL. Otherwise, you’ll need to get ready for deployment now instead of later. Deployment To deploy your Interop Forms project, you need a traditional MSI installation. Creating a setup project is covered in Chapter 34, so the details of creating your setup project aren’t repeated here. However, note a couple of special steps. In order for your new Interop Forms project to work on the client, the client needs both the .NET Framework 2.0 redistributable and the second MSI you downloaded earlier in this chapter, microsoft.interopformsredist.msi (refer to Figure B-1). If you are using Visual Studio to create your installation package, then you can add these items as prerequisites for installing your DLL via the user interface. The recommendation is to create a simple setup project in Visual Studio for installing your Interop Forms project and the associated prerequisites and have this run in advance of whatever legacy installation project you have. To extend an existing MSI, you need to carry out the appropriate steps for the tool generating your MSI, a subject beyond the scope of this appendix. Debugging When you first start planning to work with the toolkit, you might try to keep the VB6 IDE on a separate machine from your primary development machine. However, this leads to two issues. First, in order to work with the Interop Forms tools on your VB6 machine, you need to install the tools package a second time. That’s a minor issue. Second, because VB6 doesn’t know how to step into .NET applications, if you want to debug the Interop Form you created in .NET, you have a problem. The solution to this, of course, is to run both development environments on the same machine. Alternatively, you can try to create a simple Windows Forms EXE that will call and initiate your Interop Forms project from within .NET. The debugging isn’t perfect, of course, because you aren’t actually calling your code across the correct interface, but it should enable you to find most pure .NET coding issues. You can also leverage the Debug and Trace classes, but you won’t have any interactive breakpoints in that scenario. This still leaves unresolved the issue that you can’t just open Visual Studio and expect the VB6 IDE to call it when you are in Debug mode. Therefore, this section briefly discusses debugging Interop Forms Toolkit projects when you are running your VB6 application. Once you have compiled your .NET application, you have a DLL. This DLL is then exposed to your VB6 development environment and added as another COM component in your VB6 application. However, when you debug, you can’t step into this DLL from Visual Basic. Presuming you have started your Visual Basic 6.0 project so that its process is now running, your next step is to open Visual Studio and your Interop Forms project. It is hoped that you have set typical breakpoints in your source code and you might even add new breakpoints. Next, go to the Tools menu in Visual Studio and select the Attach to Process menu item. At this point, you get a dialog containing a list of running processes. Locate the “Visual Basic 6.0.exe” process. Once you have found this process, which represents the running application in VB6, attach to this process. At this point, you can work with your running application; and when the call is made into your .NET code, Visual Studio detects the call into the DLL and stops you on your breakpoint. In order for Visual Studio to detect the DLL call, you must be calling the same copy of your DLL that your Interop Forms project references. In other words, you can’t just copy it off to some other location on your local machine for installation. Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 1158 ❘ APPENDIX B Visual BasiC PowER PaCks tools VB6 Development Overall, the development process in VB6 is simple. Once you have either built your project or deployed it to the machine on which you have the VB IDE, you ’ ll need to add references to both the Microsoft Interop Form Toolkit library and your custom DLL. Keep in mind that both of the DLLs must be registered on your VB6 IDE machine in order for them to be visible. If you are building on the same machine, then they are automatically visible. Once you have added references for these libraries, you can create a new instance of your Interop Form ’ s Form class and call the standard methods and any custom methods you ’ ve exposed on that form. The one key point to remember, which was mentioned earlier but bears repeating, is that if you have created a custom constructor, in order to use it, you will call an Initialize method on your Interop Form ’ s Form class. Final Interop Tips As noted earlier in the book during the discussion of the WPF Interop controls, the Interop control packages aren ’ t perfect. Each has certain limitations that reduces its desirability for the long term. To resolve this, keep track of how much of various branches you have already converted. There will be a point where it is time to convert a larger section so that you can reduce the number of different Interop DLLs that you are using. Along these lines, note that you can ’ t put an Interop Form and an Interop user control into the same project. Each of these items needs its own DLL; and, in fact, you should consider it a best practice to only expose the DLL for a single form or control. Similarly, don ’ t plan on calling a VB6 form from within your Interop Form. The Interop logic was written to enable you to call .NET from VB6. In terms of interfaces, the Interop layer was designed to support only a minimum number of interface types. In particular, the String , Integer , and Boolean types should be at the core of what you expect to pass in terms of data. In theory, the Object type is supported, which enables you to pass custom data, so you could pass a Recordset from .NET to VB6 or vice versa; of course, VB6 doesn ’ t know about a Dataset object, so you need to reference VB6 types as the generic object. In general, the best practice is to keep your interfaces as simple as possible. When you start the VB6 IDE with your project, it attaches to your DLL. Normally this isn ’ t an issue until you fi rst run your VB6 application. At this point, you can ’ t rebuild your Interop project. The Interop project is, in fact, referenced and therefore locked by VB6. If you need to rebuild your Interop project, you need to fi rst shut down the VB6 development environment so that your code will correctly reference your latest build. As noted previously, debugging your Interop project from VB6 isn ’ t the most productive set of steps. If you change any of the method attributes, you need to regenerate the Interop wrapper classes that you generated in the last step of creating your Interop Forms project. Moreover, although it wasn ’ t covered, you can raise errors from .NET into VB6. To do this, you want to leverage the following method call on the custom My namespace that was defi ned as part of your Interop Form: My.InteropToolbox.EventMessenger.RaiseApplicationEvent("CRITICAL_ERROR", _ "Error Detail.") The other runtime issue that you may encounter is that certain internal events to your .NET application will not be triggered in the same fashion that they were in VB6. Under VB6, for example, when you referenced a If you stop and restart your VB6 application, Visual Studio will maintain the attachment, but if you close the VB6 IDE, then you ’ ll need to reattach the debugger in Visual Studio. Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com property on a Form class, this triggered the Load event on that class. Under .NET, the Load event is not fired until the form is being displayed, so you need to recognize the impact on any code that you previously set to run on the Load event. The remaining issue is related to the VB6 IDE. The IDE and VB6 don’t really recognize that if you have started a .NET DLL, there are other in-memory classes to release. For a deployed application, this isn’t an issue because when the application is closed, all of the memory associated with the process is automatically released. When you are debugging in VB6, however, the core process is associated with the IDE, not your application. As a result, the resources are not released between debugging cycles. To ensure that they are released, you can explicitly instantiate a series of code modifications contained in the Interop help files and release the .NET resources associated with your application. The recommendation is to implement these calls only after your references with the Interop tools are functioning correctly. USING THE POWER PACKS 3.0 TOOLS Unlike the Interop Forms Toolkit, the Power Packs extensions are intended to facilitate some of the same development simplicity that existed in VB6 for tasks such as printing. These classes aren’t meant to support Interop, they are meant to support migration in the sense that the code for creating simple geometric shapes or using the VB style of form printing could be implemented using syntax similar to that of VB6. After these Power Packs were released, the printing syntax was so popular that the Visual Basic team migrated those classes into the core features of Visual Studio 2008. The continued success of the 3.0 features led to the inclusion of the most of the 3.0 Power Packs classes in Service Pack 1 for Visual Studio 2008. These components, along with the repeater control continue to ship with Visual Studio 2010. Similar to the Interop Forms Toolkit, the Power PacksTools are already installed for Visual Studio 2010. For previous versions of Visual Studio then can be downloaded and installed from the Microsoft downloads. If you review a typical Windows Forms project in Visual Studio 2010, you’ll see the display shown in Figure B-4, which already includes the controls as part of your default Toolbox. Unlike the Interop Forms Toolkit, there is no need to begin with a special project template. There is no COM Interop involved because the Power Packs don’t target VB6. They target experienced VB developers who want to be able to continue to implement certain tasks in the same way they could in VB6. When your application ships, you still need to ensure that you create a dependency for the Power Packs library if you aren’t using the DLLs that are included with Visual Studio, but that’s it. Additionally, because the Power Packs are just another set of .NET libraries, there aren’t any issues related to debugging. For the sample project shown in Figure B-4, you can create a new Windows Forms application and add the PrintForm control to it. Visual Studio 2010 has a Toolbox section for the Visual Basic Power Packs, showing the OvalShape and RectangleShape shape controls along with the LineShape, Data Repeater and PrintForm controls, as shown in Figure B-4. Add a RectangleShape to the upper section of the display and an OvalShape to the center of the display. Without getting into pages of details here, using the Visual Studio designer, you should customize the look and feel of the display by adding a variety of controls. Take some time to color and fill the shape controls with a solid color. The gradient colors are defined by selecting a fill color (Coral), a FillGradientColor (Navy), a FillGradientStyle (Horizontal), and a FillStyle (Solid). All of this can and should be done within the Visual Studio designer to achieve a display similar to what is shown in Figure B-5. Using the Power Packs 3.0 Tools ❘ 1159 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 1160 ❘ APPENDIX B Visual BasiC PowER PaCks tools FIGURE B5 FIGURE B4 The application should build. The next step is to ensure that the check box in the lower-right center, labeled “Landscape” in the figure, is checked. Having done this, label the button in the bottom center of the display “Print Me” and double-click it in the Design view to trigger the automatic event handler generation. Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com The only code needed for this printing demonstration is placed within the handler for this button. The code hides the button, determines whether or not the Landscape check box is checked, and uses the Power Packs PrintForm control to Print Preview the document. Once this is completed, the Print Me button is made visible again: Private Sub ButtonPrintForm_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonPrintForm.Click ' Hide the print button since you don't want to see it in the output. ButtonPrintForm.Visible = False ' Set the printing to landscape mode by default PrintForm1.PrinterSettings.DefaultPageSettings.Landscape = CheckBox2.Checked ' Update the print action to PrintPreview so instead of wasting paper ' we see what the output would look like if sent to a printer. PrintForm1.PrintAction = Printing.PrintAction.PrintToPreview ' Execute the print logic. PrintForm1.Print(Me, PowerPacks.Printing.PrintForm.PrintOption.ClientAreaOnly) 'PrintForm1.Print() ' Restore the print button ButtonPrintForm.Visible = True End Sub Code snippet from Form1 The code shows how you can reference the PrinterSettings property, which contains the page settings to change details regarding how the page is printed. The PrintAction defines what the control should do. There are three options: print to the default/selected printer, print to a file, or use the Print Preview window. In this case, displaying the results (print preview) is the most useful option. The next line is all you need by default to print the current window. Note that this control doesn’t call the form to determine what is visible on the form. Instead, it essentially captures the current screenshot of the form for printing. The current code uses the ClientAreaOnly option, which you are encouraged to test. If you open and resize this project so that it is fairly wide, and print in profile mode, you’ll see how the control truncates the printed image (see Figure B-6). FIGURE B6 Using the Power Packs 3.0 Tools ❘ 1161 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 1162 ❘ APPENDIX B Visual BasiC PowER PaCks tools As shown in Figure B-6, the default behavior is to show the contents of the screen without the border displayed. Unfortunately, in this case the printout shows less than the full window contents. However, don’t stop at this option; try out other options. The various display options do not always capture the screen accurately, so test. In some cases the only things visible in the Print Preview window are the shape controls. However, before you print again, go to the print event handler and comment out the parameterized print line and uncomment the default print line. In this case, specify the window, which is Me, and then add one of the print options. The results, which are now correct, are shown in Figure B-7. Overall, the Power Packs shape controls enable you to easily add a custom look to your otherwise gray forms. The controls are somewhat limited, but if you want a quick and easy way to add some graphics, they do the trick. Similarly, the Print control is a quick and easy way to create a hard copy of what your application is displaying. However, keep in mind that the Print control sacrifices capabilities and customizations in order to provide a simple interface. The Power Packs 3.0 provide tools that VB6 developers can leverage for migrating an application; and for a rapid application design (RAD) prototype, they provide a dynamic and visually interesting display. Just keep in mind that when it comes to the shape controls, if you need any sort of fancy graphics, then it is recommended that you leverage the graphical capabilities provided as part of WPF. SUMMARY This appendix covered the Visual Basic Power Packs. This set of off-cycle release tools enables experienced Visual Basic developers to leverage their knowledge and existing code with the new capabilities of .NET. The Visual Basic team has created two downloadable packages that improve your ability to manage COM to .NET Interop migration and to continue to print and create graphics the same way you did before. As with all Interop-focused solutions, there are key limitations in working with the Interop Forms Toolkit, but FIGURE B7 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com in general it provides classes that will help you if you need to migrate an existing application in a controlled and cost-effective manner. In particular, this appendix highlighted the following: The focus of the Visual Basic Power Packs ➤ How to integrate Visual Basic 2010 forms with Visual Basic 6.0 applications ➤ Leveraging printing and drawing controls that behave similarly to those in Visual Basic 6.0 ➤ Although there are currently only two Power Packs, you can keep track of what is occurring in the Visual Basic Developer Center at http://msdn.microsoft.com/en-us/vbasic/default.aspx. Summary ❘ 1163 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... populate the databases If you right-click over Accounts for either database and select Show Table Data from Table from the menu, you will see a grid that enables you to add rows and initialize the values of their columns (see Figure D-6) Figure D-6 Enter two accounts in BankOfWrox Professional Visual Basic 2 010 and Professional XML and allocate $5,000 to each Now repeat the process for BankOfMe, setting... dollars for a copy of, yes, Professional Visual Basic 2 010 (a wise choice) You take the copy to the checkout and exchange a bit of cash for the book A transaction is going on here: You pay money and the store provides you with a book The important aspect of this transaction isn’t the exchange of money, but that only two reasonable outcomes are possible—either you get the book and the store gets its money... services and the runtime of WF within the host process under which ASP.NET runs — within IIS However, developing solutions using ASP.NET offers more features and requires more decisions than other Figure C-13 solutions In particular, it is possible to publish workflows as ASP.NET Web services Hosting workflows within ASP.NET solutions is similar to hosting workflows with Windows Forms, but an ASP.NET solution... changed, and many of the older activities do not have counterparts in the new version This appendix discusses the version of WF supported by the NET Framework versions 3.0 and 3.5 (i.e., Visual Basic 2005 with NET Framework 3.0 and Visual Basic 2008) This information is retained in this edition for those users who still need to maintain existing WF solutions using these older versions For new applications,... Completed event handler for the workflow, just as you did with Windows Forms Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Summary  ❘  1183 Summary While Windows Workflow Foundation does not have the visual glitz of WPF or the broad reach of WCF, it is a highly useful addition to the NET Framework Most business applications have some need for workflows, and having a standard means... application Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com D enterprise services Chapter 28 explored the vast hinterland of legacy software known as COM This appendix looks at “what COM did next” and how it fits into the world of NET, in the form of NET Enterprise Services To understand Enterprise Services,... Message Queuing (MSMQ), and Microsoft Clustering Services The aim of these developments was to increase the scalability, performance, and reliability of applications Handling transactions involved a considerable extension to the NT/COM runtime It also involved the introduction of several new standard COM interfaces, some to be used or implemented by transactional components and some to be used or implemented... understand how all the pieces fit together Let’s begin by looking at what transactions are, and how they fit into Visual Basic TransacTions A transaction is one or more linked units of processing placed together as a single unit of work, which either succeeds or fails If the unit of work succeeds, then all the work is committed If the unit fails, then every item of processing is rolled back and the... to the HandleExternalEvent activity, but the events are internal to the workflow This activity is commonly used in a state machine workflow to move between the states FaultHandler Enables handling an error within a workflow You use the FaultHandler activity to either correct or report the error gracefully For example, a timeout may occur, triggering a fault condition in the workflow This handler would... appear (see Figure D -4) Create two columns, Name and Amount, as shown Make sure that Name is set up to be the primary key When you click Close, you’ll be asked whether you want to save the changes to Table1 Select Yes, and the Choose Name dialog will appear (see Figure D-5) Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Transactional Components  ❘  Figure D -4 Use the name Accounts . the Visual Basic Power Packs ➤ How to integrate Visual Basic 2 010 forms with Visual Basic 6.0 applications ➤ Leveraging printing and drawing controls that behave similarly to those in Visual Basic. B -4, you can create a new Windows Forms application and add the PrintForm control to it. Visual Studio 2 010 has a Toolbox section for the Visual Basic Power Packs, showing the OvalShape and. discusses the version of WF supported by the .NET Framework versions 3.0 and 3.5 (i.e., Visual Basic 2005 with .NET Framework 3.0 and Visual Basic 2008). This information is retained in this

Ngày đăng: 12/08/2014, 23:23

Mục lục

  • WroxBooks

    • Professional Visual Basic 2010 and .NET 4

      • Contents

      • Introduction

      • Part I: Language Constructs and Environment

        • Chapter 1: Visual Studio 2010

          • Visual Studio 2010: Express through Ultimate

          • Visual Basic Keywords and Syntax

          • Project ProVB_VS2010

          • Enhancing a Sample Application

          • Useful Features of Visual Studio 2010

          • Summary

          • Chapter 2: Objects and Visual Basic

            • Object-Oriented Terminology

            • Working With Visual Basic Types

            • Commands: Conditional

            • Value Types (Structures)

            • Reference Types (Classes)

            • Parameter Passing

            • Variable Scope

            • Working with Objects

            • Data Type Conversions

            • Creating Classes

            • Advanced Concepts

            • Summary

Tài liệu cùng người dùng

Tài liệu liên quan